Type not narrowed on `isinstance(obj, Callable)` check
I received this bug report from a pyright user: https://github.com/microsoft/pyright/issues/2274. Mypy exhibits the same behavior, so I'm wondering whether it's a bug or the intended behavior.
In this code sample, mypy (and pyright) do not currently narrow the type of obj based on the isinstance(obj, Callable) conditional check.
from typing import Callable, Union
def some_func(obj: Union[Callable, int]):
if isinstance(obj, Callable):
reveal_type(obj) # Type of "obj" is "(*args: Unknown, **kwargs: Unknown) -> Unknown | int"
else:
reveal_type(obj) # Type of "obj" is "(*args: Unknown, **kwargs: Unknown) -> Unknown | int"
I'm wondering if there's a reason why mypy does not perform type narrowing in this case. Is there a type safety reason? Is callable() the preferred way to check for callables?
I've searched the issue database, and it doesn't appear to be an issue that anyone has previously reported.
PEP 484 says:
Because typing.Callable does double-duty as a replacement for collections.abc.Callable, isinstance(x, typing.Callable) is implemented by deferring to isinstance(x, collections.abc.Callable). However, isinstance(x, typing.Callable[...]) is not supported.
I believe mypy misses this because we generally reject typing module items from isinstance checks, and this is an exception.
@ethanhs
Thanks so much. This was driving me mad.
I was using Callable[...] | None and I just could not get type narrowing to pass isinstance(handler, Callable)
So changed this to isinstance(handler, collections.abc.Callable) and it now works fine.
Is there any reason that typing.Callable couldn't be made to work with isinstance etc in the long term?
It would make more sense.
I received this bug report from a pyright user: microsoft/pyright#2274. Mypy exhibits the same behavior, so I'm wondering whether it's a bug or the intended behavior.
@erictraut
FYI I am seeing the same behaviour in pylance v2022.7.40 / pyright 1.1.260 still have to use collections.abc.Callable
@andrewcrook, I'm not able to repro the issue in pyright or pylance. If you think there is a bug, please file a bug report in the pyright issue tracker.
@erictraut Of course I will , I was just updating you because of the link you also mentioned.
The output of Eric's sample is now
main.py:4: error: Argument 2 to "isinstance" has incompatible type "<typing special form>"; expected "_ClassInfo" [arg-type]
main.py:5: note: Revealed type is "Union[def (*Any, **Any) -> Any, builtins.int]"
main.py:7: note: Revealed type is "Union[def (*Any, **Any) -> Any, builtins.int]"
Found 1 error in 1 file (checked 1 source file)
Which is probably worse. We need to special-case Callable in some way.