Can't resolve generic type from return type of callable passed as (default) argument? #10854
Copy link
Copy link
Open
Labels
Description
UPDATE: Probably a dup of #3737 and #10504.
What I want to do is define a function that takes a callable that returns a generic as an argument where that generic influences the return type of the function. Something like this:
def doit(constructor: Callable[..., T]) -> List[T]:
...I can't seem to figure out a way to get that done, though, especially when I am supplying a default argument for the callable. Here are my test cases:
from typing import Callable, List, Protocol, TypeVar
_T = TypeVar("_T")
# error: Incompatible default for argument "constructor" (default has type "Type[str]", argument has type "Callable[[int], _T]")
def generate(n: int, constructor: Callable[[int], _T] = str) -> List[_T]:
return [constructor(i) for i in range(n)]
# error: Need type annotation for "res" (hint: "res: List[<type>] = ...")
res = generate(5) # res should be of type List[str]
assert res == ["0", "1", "2", "3", "4"]
assert generate(5, float) == [0.0, 1.0, 2.0, 3.0, 4.0]
_T_co = TypeVar("_T_co", covariant=True)
class Constructor(Protocol[_T_co]):
def __call__(self, n: int) -> _T_co:
...
def generate2(n: int, constructor: Constructor[_T] = str) -> List[_T]:
return [constructor(i) for i in range(n)]
# error: Need type annotation for "res2" (hint: "res2: List[<type>] = ...")
res2 = generate2(5, float) # res2 should be of type List[float]
assert res2 == [0.0, 1.0, 2.0, 3.0, 4.0]Runtime:
% python --version
Python 3.9.6
% python test.py
% mypy --version
mypy 0.910
% mypy --config-file=/dev/null test.py
/dev/null: No [mypy] section in config file
test.py:6: error: Incompatible default for argument "constructor" (default has type "Type[str]", argument has type "Callable[[int], _T]")
test.py:10: error: Need type annotation for "res" (hint: "res: List[<type>] = ...")
test.py:24: error: Need type annotation for "res2" (hint: "res2: List[<type>] = ...")
Found 3 errors in 1 file (checked 1 source file)
I tried searching for a similar issue, but was unable to find what I was looking for.
Activity
erictraut commentedon Jul 21, 2021
I think that your first sample (
generate) should work. That looks like a bug in mypy. It works fine in pyright.For your second sample (
generate2with theConstructorprotocol), the problem is that you've specified that the__call__method must have a parameter with the namen, but the constructors forstrandfloatdon't have a parameter with that name. If you change the__call__method to indicate that it accepts position-only parameters, it will type check without errors in both mypy and pyright. The error message provided by mypy in this case is not very useful in diagnosing the problem.posita commentedon Jul 21, 2021
Thanks @erictraut! I can verify that your suggestion fixed my simple test case. Here's one closer to the real thing (note the generic in the more complex return type):
Runtime:
This doesn't work (at least with MyPy):
This gives a new error:
erictraut commentedon Jul 22, 2021
That should type check without errors. Pyright is fine with it.
If you explicitly pass
Fractionas a second argument togenerate3, mypy is happy with it. So this appears to be a bug in mypy's handling of default parameter values.[-]Can't resolve generic type from return type of callable passed as argument?[/-][+]Can't resolve generic type from return type of callable passed as (default) argument?[/+]posita commentedon Jul 22, 2021
Whoo boy.
☝️ That is some work-around!
Thanks for all your help narrowing this down!
hauntsaninja commentedon Jul 22, 2021
Linking #3737
posita commentedon Jul 22, 2021
Thanks @hauntsaninja! It does look like this has overlap with #3737, and is probably even a dup, since removing the default argument works, even for the non-protocol version of my simple test:
☝️ No errors with that one.
In any event, this definitely feels like a dupe of #10504, which I just now found (via #3737).
posita commentedon Aug 2, 2023
Somewhere between 0.993 and 1.4.1 (yes, I know that's a big range), the work-around identified in #10854 (comment) no longer works. 😞 It results in:
posita commentedon Aug 3, 2023
New work-around (using
Nonefor the default argument andoverloadto narrow the return type when omitted):