Skip to content

Commit 99dc88d

Browse files
[3.13] gh-132467: Document and test that generic aliases are not classes (GH-133504) (#151117)
(cherry picked from commit 5915a1f) Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
1 parent fc600e4 commit 99dc88d

3 files changed

Lines changed: 88 additions & 53 deletions

File tree

Doc/library/stdtypes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5610,6 +5610,15 @@ creation::
56105610
>>> type(l)
56115611
<class 'list'>
56125612

5613+
5614+
Instances of ``GenericAlias`` are not classes at runtime, even though they behave like classes (they can be instantiated and subclassed)::
5615+
5616+
>>> import inspect
5617+
>>> inspect.isclass(list[int])
5618+
False
5619+
5620+
This is true for :ref:`user-defined generics <user-defined-generics>` also.
5621+
56135622
Calling :func:`repr` or :func:`str` on a generic shows the parameterized type::
56145623

56155624
>>> repr(list[int])

Lib/test/test_typing.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5583,6 +5583,27 @@ def foo(x: T):
55835583

55845584
foo(42)
55855585

5586+
def test_genericalias_instance_isclass(self):
5587+
# test against user-defined generic classes
5588+
T = TypeVar('T')
5589+
5590+
class Node(Generic[T]):
5591+
def __init__(self, label: T,
5592+
left: 'Node[T] | None' = None,
5593+
right: 'Node[T] | None' = None):
5594+
self.label = label
5595+
self.left = left
5596+
self.right = right
5597+
5598+
self.assertTrue(inspect.isclass(Node))
5599+
self.assertFalse(inspect.isclass(Node[int]))
5600+
self.assertFalse(inspect.isclass(Node[str]))
5601+
5602+
# test against standard generic classes
5603+
self.assertFalse(inspect.isclass(set[int]))
5604+
self.assertFalse(inspect.isclass(list[bytes]))
5605+
self.assertFalse(inspect.isclass(dict[str, str]))
5606+
55865607
def test_implicit_any(self):
55875608
T = TypeVar('T')
55885609

Lib/typing.py

Lines changed: 58 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,31 +1410,35 @@ def __dir__(self):
14101410

14111411

14121412
class _GenericAlias(_BaseGenericAlias, _root=True):
1413-
# The type of parameterized generics.
1414-
#
1415-
# That is, for example, `type(List[int])` is `_GenericAlias`.
1416-
#
1417-
# Objects which are instances of this class include:
1418-
# * Parameterized container types, e.g. `Tuple[int]`, `List[int]`.
1419-
# * Note that native container types, e.g. `tuple`, `list`, use
1420-
# `types.GenericAlias` instead.
1421-
# * Parameterized classes:
1422-
# class C[T]: pass
1423-
# # C[int] is a _GenericAlias
1424-
# * `Callable` aliases, generic `Callable` aliases, and
1425-
# parameterized `Callable` aliases:
1426-
# T = TypeVar('T')
1427-
# # _CallableGenericAlias inherits from _GenericAlias.
1428-
# A = Callable[[], None] # _CallableGenericAlias
1429-
# B = Callable[[T], None] # _CallableGenericAlias
1430-
# C = B[int] # _CallableGenericAlias
1431-
# * Parameterized `Final`, `ClassVar`, `TypeGuard`, and `TypeIs`:
1432-
# # All _GenericAlias
1433-
# Final[int]
1434-
# ClassVar[float]
1435-
# TypeGuard[bool]
1436-
# TypeIs[range]
1437-
1413+
"""The type of parameterized generics.
1414+
1415+
That is, for example, `type(List[int])` is `_GenericAlias`.
1416+
1417+
Objects which are instances of this class include:
1418+
* Parameterized container types, e.g. `Tuple[int]`, `List[int]`.
1419+
* Note that native container types, e.g. `tuple`, `list`, use
1420+
`types.GenericAlias` instead.
1421+
* Parameterized classes:
1422+
class C[T]: pass
1423+
# C[int] is a _GenericAlias
1424+
* `Callable` aliases, generic `Callable` aliases, and
1425+
parameterized `Callable` aliases:
1426+
T = TypeVar('T')
1427+
# _CallableGenericAlias inherits from _GenericAlias.
1428+
A = Callable[[], None] # _CallableGenericAlias
1429+
B = Callable[[T], None] # _CallableGenericAlias
1430+
C = B[int] # _CallableGenericAlias
1431+
* Parameterized `Final`, `ClassVar`, `TypeForm`, `TypeGuard`, and `TypeIs`:
1432+
# All _GenericAlias
1433+
Final[int]
1434+
ClassVar[float]
1435+
TypeForm[bytearray]
1436+
TypeGuard[bool]
1437+
TypeIs[range]
1438+
1439+
Note that instances of this class are not classes (e.g by `inspect.isclass`),
1440+
even though they behave like them.
1441+
"""
14381442
def __init__(self, origin, args, *, inst=True, name=None):
14391443
super().__init__(origin, inst=inst, name=name)
14401444
if not isinstance(args, tuple):
@@ -1466,20 +1470,21 @@ def __ror__(self, left):
14661470

14671471
@_tp_cache
14681472
def __getitem__(self, args):
1469-
# Parameterizes an already-parameterized object.
1470-
#
1471-
# For example, we arrive here doing something like:
1472-
# T1 = TypeVar('T1')
1473-
# T2 = TypeVar('T2')
1474-
# T3 = TypeVar('T3')
1475-
# class A(Generic[T1]): pass
1476-
# B = A[T2] # B is a _GenericAlias
1477-
# C = B[T3] # Invokes _GenericAlias.__getitem__
1478-
#
1479-
# We also arrive here when parameterizing a generic `Callable` alias:
1480-
# T = TypeVar('T')
1481-
# C = Callable[[T], None]
1482-
# C[int] # Invokes _GenericAlias.__getitem__
1473+
"""Parameterizes an already-parameterized object.
1474+
1475+
For example, we arrive here doing something like:
1476+
T1 = TypeVar('T1')
1477+
T2 = TypeVar('T2')
1478+
T3 = TypeVar('T3')
1479+
class A(Generic[T1]): pass
1480+
B = A[T2] # B is a _GenericAlias
1481+
C = B[T3] # Invokes _GenericAlias.__getitem__
1482+
1483+
We also arrive here when parameterizing a generic `Callable` alias:
1484+
T = TypeVar('T')
1485+
C = Callable[[T], None]
1486+
C[int] # Invokes _GenericAlias.__getitem__
1487+
"""
14831488

14841489
if self.__origin__ in (Generic, Protocol):
14851490
# Can't subscript Generic[...] or Protocol[...].
@@ -1496,20 +1501,20 @@ def __getitem__(self, args):
14961501
return r
14971502

14981503
def _determine_new_args(self, args):
1499-
# Determines new __args__ for __getitem__.
1500-
#
1501-
# For example, suppose we had:
1502-
# T1 = TypeVar('T1')
1503-
# T2 = TypeVar('T2')
1504-
# class A(Generic[T1, T2]): pass
1505-
# T3 = TypeVar('T3')
1506-
# B = A[int, T3]
1507-
# C = B[str]
1508-
# `B.__args__` is `(int, T3)`, so `C.__args__` should be `(int, str)`.
1509-
# Unfortunately, this is harder than it looks, because if `T3` is
1510-
# anything more exotic than a plain `TypeVar`, we need to consider
1511-
# edge cases.
1512-
1504+
"""Determines new __args__ for __getitem__.
1505+
1506+
For example, suppose we had:
1507+
T1 = TypeVar('T1')
1508+
T2 = TypeVar('T2')
1509+
class A(Generic[T1, T2]): pass
1510+
T3 = TypeVar('T3')
1511+
B = A[int, T3]
1512+
C = B[str]
1513+
`B.__args__` is `(int, T3)`, so `C.__args__` should be `(int, str)`.
1514+
Unfortunately, this is harder than it looks, because if `T3` is
1515+
anything more exotic than a plain `TypeVar`, we need to consider
1516+
edge cases.
1517+
"""
15131518
params = self.__parameters__
15141519
# In the example above, this would be {T3: str}
15151520
for param in params:

0 commit comments

Comments
 (0)