Skip to content

isinstance + class indirectly deriving from SupportsInt results in "TypeError: Protocols cannot be used with isinstance()" #297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jstasiak opened this issue Oct 12, 2016 · 4 comments · Fixed by #330

Comments

@jstasiak
Copy link
Contributor

jstasiak commented Oct 12, 2016

The following assumes mypy 0.4.5 and CPython 3.5.2.

Let's have the code:

# code3.py
class C1:

    def __int__(self) -> int:
        return 42


class C2(C1):
    pass


c = C2()
print(int(c))
print(isinstance(c, C1))

The code works:

% python code3.py 
42
True

mypy, however, will complain (as expected):

% mypy code3.py  
code3.py:12: error: Argument 1 to "int" has incompatible type "C2"; expected "Union[SupportsInt, str, bytes]"

Let's try to make mypy happy:

# code3.py
from typing import SupportsInt


class C1(SupportsInt):

    def __int__(self) -> int:
        return 42


class C2(C1):
    pass


c = C2()
print(int(c))
print(isinstance(c, C1))

Now mypy is happy but the code fails at runtime:

% python code3.py
42
Traceback (most recent call last):
  File "code3.py", line 16, in <module>
    print(isinstance(c, C1))
  File "/usr/local/Cellar/python3/3.5.2_2/Frameworks/Python.framework/Versions/3.5/lib/python3.5/typing.py", line 1269, in __instancecheck__
    raise TypeError("Protocols cannot be used with isinstance().")
TypeError: Protocols cannot be used with isinstance().

It seems that I'm put in a situation where I have to choose between static type safety and having the code actually work, am I missing something here? If not - what's the recommended solution to a problem like this?

@gvanrossum
Copy link
Member

This is definitely a problem. A quick workaround would be to replace the import with

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from typing import SupportsInt
else:
    SupportsInt = object

but that's pretty gross.

I have a pretty straightforward fix, which still disallows isinstance() checks on classes that directly derive from _Protocol (such as SupportsAbs) but allows it for subclasses thereof (like your C1 and C2).

diff --git a/src/typing.py b/src/typing.py
index c4f0fdc..a87500c 100644
--- a/src/typing.py
+++ b/src/typing.py
@@ -1440,6 +1440,8 @@ class _ProtocolMeta(GenericMeta):
     """

     def __instancecheck__(self, obj):
+        if _Protocol not in self.__bases__:
+            return super().__instancecheck__(obj)
         raise TypeError("Protocols cannot be used with isinstance().")

     def __subclasscheck__(self, cls):

@ilevkivskyi What do you think of that fix?

@gvanrossum
Copy link
Member

PS. The fix only works on top of #295.

@ilevkivskyi
Copy link
Member

@gvanrossum As a quick fix this is perfectly OK. I would say that for a permanent fix we could think about making the rules the same as for ordinary generics. But before deciding on permanent solution, I would ask @JukkaL for his opinion (the permanent fix would probably depend on resolution of #11).

@jstasiak
Copy link
Contributor Author

Thanks @gvanrossum, I totally forgot about the TYPE_CHECKING variable, I'll use the workaround you mentioned for the time being.

gvanrossum pushed a commit that referenced this issue Nov 19, 2016
Fixes #297

This allows to use isinstance() with classes that inherit from protocols in typing.py such as SupportsInt etc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants