I'm defining functions inside a condition based on a variables type, a Union of other types. mypy infers that the type is narrowed down in this context, but seems to throw this knowledge away.
I don't understand why. Is this a bug in mypy?
Consider this:
from typing import Union, Callable
def test(a: Union[None, Callable]):
if a is None:
pass
else:
def test0():
a()
def test1(b: str):
a(b)
callable_a = a
def test2(b: str):
callable_a(b)
a()
a('foo')
mypy's output:
test.py:9: error: "None" not callable
Found 1 error in 1 file (checked 1 source file)
line 9 is the body of test1. Seems that here the whole Union[None, Callable]
is applied
("the whole union" is not clear from this example, in the actual code it's Union[None, Sequence, Callable] and mypy objects that both None and Sequence are not callable).
outside function definitions, the problem does not appear.
Some thoughts:
callable_a
can only ever be a callable, so I'm a bit more willing to accept that mypy doesn't complain about this. I don't understand why it's complaining anyway.test0
and test1
call a
, the difference is only the argument. Why does this make a difference to mypy, especially here when the signature of a
is undefined?I'm running mypy 0.950 if it makes a difference.
This is correct behavior which is well explained in the mypy
docs. Inference of a
would be simply wrong because of late binding.
Here's the example given in the docs:
from typing import Callable, Optional
def foo(x: Optional[int]) -> Callable[[], int]:
if x is None:
x = 5
print(x + 1) # mypy correctly deduces x must be an int here
def inner() -> int:
return x + 1 # but (correctly) complains about this line
x = None # because x could later be assigned None
return inner
inner = foo(5)
inner() # this will raise an error when called
Now about your code: First, functions without annotations are not checked, so you don't see all real errors. After adding type hints, we have the following:
from typing import Union, Callable
def test(a: Union[None, Callable]):
if a is None:
pass
else:
def test0() -> None:
a() # E: "None" not callable
def test1(b: str) -> None:
a(b) # E: "None" not callable
callable_a = a
reveal_type(callable_a) # N: Revealed type is "def (*Any, **Any) -> Any"
def test2(b: str) -> None:
callable_a(b)
a()
a('foo')
Now only test2
is valid, which is expected (see revealed type). callable_a
is inferred to be Callable
only from is None
type guard, so you cannot assign None
to it later. You can fix this with assert callable(a)
inside test0
and test1
(dirty way) or use the same pattern as with test2
. Default argument binding (using def test0(a: Callable = a)
should be valid too but mypy
doesn't like it for some reason - it is a false positive, but probably too hard to deal with or devs don't have time for it now.
Look through this issue if you are interested in mypy
's opinion on that. Here's a playground to play with this online.