Running the below code with Python 3.12.4:
from typing import Generic, TypeVar
T = TypeVar("T")
class Foo(Generic[T]):
def some_method(self) -> T:
pass
isinstance(Foo[int](), Foo[int])
It will throw a TypeError: Subscripted generics cannot be used with class and instance checks
.
Traceback (most recent call last):
File "/path/to/a.py", line 9, in <module>
isinstance(Foo[int](), Foo[int])
File "/path/to/.pyenv/versions/3.12.4/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/typing.py", line 1213, in __instancecheck__
return self.__subclasscheck__(type(obj))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/path/to/.pyenv/versions/3.12.4/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/typing.py", line 1216, in __subclasscheck__
raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks
What was the rationale for Python not allowing isinstance
checks with Generic
s?
At runtime, type arguments are not verifiable. Such relationships are only checked at type-checking time:
Note that the runtime type (class) of [the variables given as example] is still just
Node
āNode[int]
andNode[str]
are distinguishable class objects, but the runtime class of the objects created by instantiating them doesn't record the distinction. This behavior is called "type erasure"; it is common practice in languages with generics (e.g. Java, TypeScript).
For example, if isinstance(..., list[str])
were to be treated at isintansce(..., list)
, the result would be very confusing:
>>> a = [1, 2, 3]
>>> isinstance(a, list[str])
True
Also consider the case where type arguments are not concrete:
def is_list_of(v: Any, element_type: type[T]) -> TypeIs[list[T]]:
return isinstance(v, list[T]) # ???
This has always been the case since PEP 484, albeit not very explicitly (Callable and Generics are two different sections, and this note seems to mean something slightly different than what we are taking it to be, though conceptually the same):
[...]
isinstance(x, typing.Callable[...])
is not supported.
However, there are runtime type checkers that do this kind of heavy-lifting, such as Pydantic.
Historically, the PEP 484 note was added in this commit, and type-erasure-related problems were discussed in this GitHub issue. It seems that GvR and other contributors were initially opposed to Node[int]()
(which then worked at runtime but very slow, and was forbidden by Mypy) due to performance problems.