'"Protocols cannot be used with isinstance()" - why not?

The new typing module contains several objects with names like "SupportsInt" (-Float, -Bytes, etc.). The name, and the descriptions on the documentation page for the module, might be read to suggest that you can test whether an object is of a type that "supports __int__()". But if you try to use isinstance(), it gives a response that makes it clear that that isn't something you are meant to do:

>>> isinstance(5, typing.SupportsInt)
(Traceback omitted)
TypeError: Protocols cannot be used with isinstance().

On the other hand, you can use issubclass():

>>> issubclass((5).__class__, typing.SupportsInt)
True
>>> issubclass(type(5), typing.SupportsInt)
True

What is a "protocol" in this context? Why does it disallow the use of isinstance() in this way?



Solution 1:[1]

This is all of the reasoning given in PEP 484, the PEP for the typing module:

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.

A protocol is also known as a magic method. These are most of the python protocols (full list here):

>>> dir(object)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', 
'__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', 
'__setattr__', '__sizeof__', '__str__', '__subclasshook__']

I have not found any clear reason for why typing does not support isinstance. The reason that issubclass works is that isinstance uses the __class_ protocol which is not allowed in typing, while issubclass uses the __subclasshook__ protocol which is allowed. I believe the reason is that the functionality was already coded in collections.abc.Callable and they did not want to recode it in the typing module.

Solution 2:[2]

If you reached this question wanting to get around the error (like I did), the solution is to decorate the protocol class with @runtime_checkable:

from typing import Protocol, runtime_checkable

@runtime_checkable
class StorageProtocol(Protocol):
    ...

@typing.runtime_checkable

Mark a protocol class as a runtime protocol.

Such a protocol can be used with isinstance() and issubclass().

storage = ...  # get something that must implement StorageProtocol
if not isinstance(storage, StorageProtocol):
    raise RuntimeError(...)

Solution 3:[3]

As the documentation says: At runtime, isinstance(x, T) will raise TypeError. In general, isinstance() and issubclass() should not be used with types. ( https://docs.python.org/3/library/typing.html?highlight=typing#typing.TypeVar )

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 rassar
Solution 2 Alex Peters
Solution 3 Duncan MC Leod