Skip to content

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

Closed

Description

@jstasiak

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?

Activity

  1. gvanrossum commented on Oct 12, 2016

    @gvanrossum
    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?

  2. gvanrossum commented on Oct 12, 2016

    @gvanrossum
    Member

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

  3. ilevkivskyi commented on Oct 12, 2016

    @ilevkivskyi
    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).

  4. jstasiak commented on Oct 13, 2016

    @jstasiak
    ContributorAuthor

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

  5. added a commit that references this issue on Nov 19, 2016
    1881ffd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions