From 8f45c6975ecfe75687ef6734f467074261a0036d Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 2 Apr 2026 10:49:57 +0200 Subject: [PATCH 1/5] [operator] Replace or explain Any --- stdlib/operator.pyi | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/stdlib/operator.pyi b/stdlib/operator.pyi index 2f919514b0b8..9e67bca49cd9 100644 --- a/stdlib/operator.pyi +++ b/stdlib/operator.pyi @@ -53,7 +53,7 @@ from _operator import ( xor as xor, ) from _typeshed import SupportsGetItem -from typing import Any, Generic, TypeVar, final, overload +from typing import Any, Generic, ParamSpec, TypeVar, final, overload from typing_extensions import Self, TypeVarTuple, Unpack _T = TypeVar("_T") @@ -61,6 +61,8 @@ _T_co = TypeVar("_T_co", covariant=True) _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _Ts = TypeVarTuple("_Ts") +_P = ParamSpec("_P", default=...) +_R = TypeVar("_R", default=Any) __all__ = [ "abs", @@ -182,6 +184,8 @@ if sys.version_info >= (3, 11): # them here. @final class attrgetter(Generic[_T_co]): + # We can't determine the type of the attribute(s) being accessed statucally, + # so we have to use Any for the return type. @overload def __new__(cls, attr: str, /) -> attrgetter[Any]: ... @overload @@ -192,6 +196,8 @@ class attrgetter(Generic[_T_co]): def __new__(cls, attr: str, attr2: str, attr3: str, attr4: str, /) -> attrgetter[tuple[Any, Any, Any, Any]]: ... @overload def __new__(cls, attr: str, /, *attrs: str) -> attrgetter[tuple[Any, ...]]: ... + # obj needs to have the named attribute(s) with the correct type. + # Unfortunately, we can't check that statically, so we need to use Any. def __call__(self, obj: Any, /) -> _T_co: ... @final @@ -212,6 +218,8 @@ class itemgetter(Generic[_T_co]): def __call__(self, obj: SupportsGetItem[Any, Any]) -> Any: ... @final -class methodcaller: - def __new__(cls, name: str, /, *args: Any, **kwargs: Any) -> Self: ... - def __call__(self, obj: Any) -> Any: ... +class methodcaller(Generic[_P, _R]): + def __new__(cls, name: str, /, *args: _P.args, **kwargs: _P.kwargs) -> Self: ... + # obj needs to have the named method with the correct signature. + # Unfortunately, we can't check that statically, so we need to use Any. + def __call__(self, obj: Any) -> _R: ... From 175e0531f2abe9e6d3f16e19544e4ef47544c12d Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 2 Apr 2026 11:07:36 +0200 Subject: [PATCH 2/5] Add tests --- stdlib/@tests/test_cases/check_operator.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 stdlib/@tests/test_cases/check_operator.py diff --git a/stdlib/@tests/test_cases/check_operator.py b/stdlib/@tests/test_cases/check_operator.py new file mode 100644 index 000000000000..95602031ec99 --- /dev/null +++ b/stdlib/@tests/test_cases/check_operator.py @@ -0,0 +1,11 @@ +from operator import methodcaller +from typing import Any +from typing_extensions import assert_type + +m1 = methodcaller("foo") +assert_type(m1, methodcaller[[], Any]) +m2 = methodcaller("foo", 42, bar="") +# assert_type(m2, methodcaller[[int, Arg(str, "bar")], Any]) +m3: methodcaller[[], int] = methodcaller("foo") # ok +m4: methodcaller[[int], Any] = methodcaller("foo", 1) # ok +m5: methodcaller[[str], int] = methodcaller("foo", 1) # type: ignore From 7e67f035b599cd4aebfb948733bfba212bf663e2 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 2 Apr 2026 11:14:58 +0200 Subject: [PATCH 3/5] Fix ParamSpec default --- stdlib/operator.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/operator.pyi b/stdlib/operator.pyi index 9e67bca49cd9..5e086dc38c5f 100644 --- a/stdlib/operator.pyi +++ b/stdlib/operator.pyi @@ -61,7 +61,7 @@ _T_co = TypeVar("_T_co", covariant=True) _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _Ts = TypeVarTuple("_Ts") -_P = ParamSpec("_P", default=...) +_P = ParamSpec("_P", default=[]) _R = TypeVar("_R", default=Any) __all__ = [ From 602bf14846ea97bf0326a608b2fc99d910c13596 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 2 Apr 2026 11:31:38 +0200 Subject: [PATCH 4/5] Fix type arguments --- stdlib/operator.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/operator.pyi b/stdlib/operator.pyi index 5e086dc38c5f..1d80a2138fb2 100644 --- a/stdlib/operator.pyi +++ b/stdlib/operator.pyi @@ -61,7 +61,7 @@ _T_co = TypeVar("_T_co", covariant=True) _T1 = TypeVar("_T1") _T2 = TypeVar("_T2") _Ts = TypeVarTuple("_Ts") -_P = ParamSpec("_P", default=[]) +_P = ParamSpec("_P", default=...) _R = TypeVar("_R", default=Any) __all__ = [ @@ -219,7 +219,7 @@ class itemgetter(Generic[_T_co]): @final class methodcaller(Generic[_P, _R]): - def __new__(cls, name: str, /, *args: _P.args, **kwargs: _P.kwargs) -> Self: ... + def __new__(cls, name: str, /, *args: _P.args, **kwargs: _P.kwargs) -> methodcaller[_P, Any]: ... # obj needs to have the named method with the correct signature. # Unfortunately, we can't check that statically, so we need to use Any. def __call__(self, obj: Any) -> _R: ... From 2039f0faa513b77182a6b6a8ec2d4d786521d4a5 Mon Sep 17 00:00:00 2001 From: Sebastian Rittau Date: Thu, 2 Apr 2026 11:33:43 +0200 Subject: [PATCH 5/5] Remove unused import --- stdlib/operator.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/operator.pyi b/stdlib/operator.pyi index 1d80a2138fb2..3643151ef348 100644 --- a/stdlib/operator.pyi +++ b/stdlib/operator.pyi @@ -54,7 +54,7 @@ from _operator import ( ) from _typeshed import SupportsGetItem from typing import Any, Generic, ParamSpec, TypeVar, final, overload -from typing_extensions import Self, TypeVarTuple, Unpack +from typing_extensions import TypeVarTuple, Unpack _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True)