-
To avoid repeating myself too much, I sometimes like to create helper functions that return a new function which is intended to be used as a method. The "method creating function" would typically take in some arguments. It would then create a new function that receives What I'm looking for, is a way to:
Using CallableTyping this using from collections.abc import Callable
from typing import Any
def create_int_converter() -> Callable[[Any, str], int]:
return lambda self, s: int(s)
class ClassWithDynamicallyCreatedMethod:
convert_to_int = create_int_converter()
o = ClassWithDynamicallyCreatedMethod()
converted: int = o.convert_to_int("123")
print(repr(converted)) Mypy: ✅ Using callback protocol without extra self argumentHowever, I'd like to define overloads for the generated method. To do this, I thought I could build on the concept of "Callback protocols" as mentioned in PEP 544: from typing import Any, Protocol
class ConverterProtocol(Protocol):
def __call__(self, s: str) -> int: ...
def create_int_converter() -> ConverterProtocol:
def converter(self: Any, s: str) -> int:
return int(s)
return converter
class ClassWithDynamicallyCreatedMethod:
greeting = "Hi there!"
convert_to_int = create_int_converter()
o = ClassWithDynamicallyCreatedMethod()
converted: int = o.convert_to_int("123")
print(repr(converted)) Mypy: ❌ Mypy error:
Using callback protocol with extra self argumentI tried experimenting with adding an explicit from typing import Any, Protocol
class ConverterProtocol(Protocol):
def __call__(self_, self: Any, s: str) -> int: ...
def create_int_converter() -> ConverterProtocol:
def converter(self: Any, s: str) -> int:
return int(s)
return converter
class ClassWithDynamicallyCreatedMethod:
greeting = "Hi there!"
convert_to_int = create_int_converter()
o = ClassWithDynamicallyCreatedMethod()
converted: int = o.convert_to_int("123")
print(repr(converted)) Mypy: ❌ Mypy error:
ThoughtsI first thought this would be a bug in Mypy. And maybe it is? But I'm also thinking there is a nuanced difference between how a function and a callable user object are treated with respect to the first I'm starting to think that the "callback protocol" approach is not usable for this usecase, because an instance of a class with a The following two Mypy issues may be related: python/mypy#708 and python/mypy#5485 but I don't think they address the inconsistencies the latter two examples demonstrate? I'll consider opening issues on that later. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 8 replies
-
That's correct. When Python looks up a method from an instance, it calls Consider this, for example:
For function objects, the To tell mypy that your object isn't just a callable, but a method, you need to explain to mypy that |
Beta Was this translation helpful? Give feedback.
-
@tobinus @Akuli from typing import *
class Test1:
@overload
def f(self, key: Literal['A']) -> int: ...
@overload
def f(self, key: Literal['B']) -> str: ...
def f(self, key: Any) -> Any:
if key == 'A': return 1
elif key == 'B': return 'x'
assert_never(key)
class Test2:
def __init__(self, arg: Test1):
self._arg = arg
def get_arg(self) -> Test1:
return self._arg
# not sure how to deduce the type signature from Test1.f
def f(self, key):
return self.get_arg().f(key)
test2 = Test2(Test1())
# expecting the same type
reveal_type(test2.get_arg().f)
reveal_type(test2.f) ... but I could not make it work with With this approach, I get T = TypeVar('T')
def same_as(f: T) -> Callable[[Callable[..., Any]], T]:
def wrap(_g): # ignore wrapped function, use 'f'
def wrapped_f(*args, **kwargs):
return f(*args, **kwargs)
return wrapped_f
return wrap
class Test2:
...
@same_as(Test1.f)
def f(self, key):
... But If I try with a protocol, like this: from typing import *
P = ParamSpec("P")
R = TypeVar("R", covariant=True)
class Method(Protocol, Generic[P, R]):
def __get__(self, instance: Any, owner: type | None = None) -> Callable[P, R]:
...
def __call__(self_, self: Any, *args: P.args, **kwargs: P.kwargs) -> R:
...
def same_as(f: Method[P, R]) -> Callable[[Callable[..., Any]], Method[P, R]]:
raise NotImplementedError
class Test1:
@overload
def f(self, key: Literal['A']) -> int: ...
@overload
def f(self, key: Literal['B']) -> str: ...
def f(self, key: Any) -> Any:
if key == 'A': return 1
elif key == 'B': return 'x'
assert_never(key)
class Test2:
def __init__(self, arg: Test1):
self._arg = arg
def get_arg(self) -> Test1:
return self._arg
@same_as(Test1.f)
def f(self, key: Any) -> Any:
...
test2 = Test2(Test1())
# expecting the same type
reveal_type(test2.get_arg().f)
reveal_type(test2.f) ... I get the wrong type for
I would appreciate any suggestion how to resolve the problem, that is: |
Beta Was this translation helpful? Give feedback.
That's correct. When Python looks up a method from an instance, it calls
__get__
to get a method object, which it then__call__()
s. If there is no__get__
, the object is called as is, without passingself
.Consider this, for example: