[3.13] gh-132467: Document and test that generic aliases are not classes (GH-133504) (#151117)

(cherry picked from commit 5915a1fb9d)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
Abduaziz π 2026-06-10 01:24:02 +05:00 committed by GitHub
parent fc600e4178
commit 99dc88db09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 86 additions and 51 deletions

View file

@ -5610,6 +5610,15 @@ creation::
>>> type(l)
<class 'list'>
Instances of ``GenericAlias`` are not classes at runtime, even though they behave like classes (they can be instantiated and subclassed)::
>>> import inspect
>>> inspect.isclass(list[int])
False
This is true for :ref:`user-defined generics <user-defined-generics>` also.
Calling :func:`repr` or :func:`str` on a generic shows the parameterized type::
>>> repr(list[int])

View file

@ -5583,6 +5583,27 @@ def foo(x: T):
foo(42)
def test_genericalias_instance_isclass(self):
# test against user-defined generic classes
T = TypeVar('T')
class Node(Generic[T]):
def __init__(self, label: T,
left: 'Node[T] | None' = None,
right: 'Node[T] | None' = None):
self.label = label
self.left = left
self.right = right
self.assertTrue(inspect.isclass(Node))
self.assertFalse(inspect.isclass(Node[int]))
self.assertFalse(inspect.isclass(Node[str]))
# test against standard generic classes
self.assertFalse(inspect.isclass(set[int]))
self.assertFalse(inspect.isclass(list[bytes]))
self.assertFalse(inspect.isclass(dict[str, str]))
def test_implicit_any(self):
T = TypeVar('T')

View file

@ -1410,31 +1410,35 @@ def __dir__(self):
class _GenericAlias(_BaseGenericAlias, _root=True):
# The type of parameterized generics.
#
# That is, for example, `type(List[int])` is `_GenericAlias`.
#
# Objects which are instances of this class include:
# * Parameterized container types, e.g. `Tuple[int]`, `List[int]`.
# * Note that native container types, e.g. `tuple`, `list`, use
# `types.GenericAlias` instead.
# * Parameterized classes:
# class C[T]: pass
# # C[int] is a _GenericAlias
# * `Callable` aliases, generic `Callable` aliases, and
# parameterized `Callable` aliases:
# T = TypeVar('T')
# # _CallableGenericAlias inherits from _GenericAlias.
# A = Callable[[], None] # _CallableGenericAlias
# B = Callable[[T], None] # _CallableGenericAlias
# C = B[int] # _CallableGenericAlias
# * Parameterized `Final`, `ClassVar`, `TypeGuard`, and `TypeIs`:
# # All _GenericAlias
# Final[int]
# ClassVar[float]
# TypeGuard[bool]
# TypeIs[range]
"""The type of parameterized generics.
That is, for example, `type(List[int])` is `_GenericAlias`.
Objects which are instances of this class include:
* Parameterized container types, e.g. `Tuple[int]`, `List[int]`.
* Note that native container types, e.g. `tuple`, `list`, use
`types.GenericAlias` instead.
* Parameterized classes:
class C[T]: pass
# C[int] is a _GenericAlias
* `Callable` aliases, generic `Callable` aliases, and
parameterized `Callable` aliases:
T = TypeVar('T')
# _CallableGenericAlias inherits from _GenericAlias.
A = Callable[[], None] # _CallableGenericAlias
B = Callable[[T], None] # _CallableGenericAlias
C = B[int] # _CallableGenericAlias
* Parameterized `Final`, `ClassVar`, `TypeForm`, `TypeGuard`, and `TypeIs`:
# All _GenericAlias
Final[int]
ClassVar[float]
TypeForm[bytearray]
TypeGuard[bool]
TypeIs[range]
Note that instances of this class are not classes (e.g by `inspect.isclass`),
even though they behave like them.
"""
def __init__(self, origin, args, *, inst=True, name=None):
super().__init__(origin, inst=inst, name=name)
if not isinstance(args, tuple):
@ -1466,20 +1470,21 @@ def __ror__(self, left):
@_tp_cache
def __getitem__(self, args):
# Parameterizes an already-parameterized object.
#
# For example, we arrive here doing something like:
# T1 = TypeVar('T1')
# T2 = TypeVar('T2')
# T3 = TypeVar('T3')
# class A(Generic[T1]): pass
# B = A[T2] # B is a _GenericAlias
# C = B[T3] # Invokes _GenericAlias.__getitem__
#
# We also arrive here when parameterizing a generic `Callable` alias:
# T = TypeVar('T')
# C = Callable[[T], None]
# C[int] # Invokes _GenericAlias.__getitem__
"""Parameterizes an already-parameterized object.
For example, we arrive here doing something like:
T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')
class A(Generic[T1]): pass
B = A[T2] # B is a _GenericAlias
C = B[T3] # Invokes _GenericAlias.__getitem__
We also arrive here when parameterizing a generic `Callable` alias:
T = TypeVar('T')
C = Callable[[T], None]
C[int] # Invokes _GenericAlias.__getitem__
"""
if self.__origin__ in (Generic, Protocol):
# Can't subscript Generic[...] or Protocol[...].
@ -1496,20 +1501,20 @@ def __getitem__(self, args):
return r
def _determine_new_args(self, args):
# Determines new __args__ for __getitem__.
#
# For example, suppose we had:
# T1 = TypeVar('T1')
# T2 = TypeVar('T2')
# class A(Generic[T1, T2]): pass
# T3 = TypeVar('T3')
# B = A[int, T3]
# C = B[str]
# `B.__args__` is `(int, T3)`, so `C.__args__` should be `(int, str)`.
# Unfortunately, this is harder than it looks, because if `T3` is
# anything more exotic than a plain `TypeVar`, we need to consider
# edge cases.
"""Determines new __args__ for __getitem__.
For example, suppose we had:
T1 = TypeVar('T1')
T2 = TypeVar('T2')
class A(Generic[T1, T2]): pass
T3 = TypeVar('T3')
B = A[int, T3]
C = B[str]
`B.__args__` is `(int, T3)`, so `C.__args__` should be `(int, str)`.
Unfortunately, this is harder than it looks, because if `T3` is
anything more exotic than a plain `TypeVar`, we need to consider
edge cases.
"""
params = self.__parameters__
# In the example above, this would be {T3: str}
for param in params: