Skip to content

Commit 54bd7c0

Browse files
miss-islingtonAbduazizZiyodovJelleZijlstra
authored
[3.15] gh-132467: Document and test that generic aliases are not classes (GH-133504) (#150854)
gh-132467: Document and test that generic aliases are not classes (GH-133504) (cherry picked from commit 5915a1f) Co-authored-by: Abduaziz π <mail@ziyodov.uz> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
1 parent 24f5d3d commit 54bd7c0

3 files changed

Lines changed: 88 additions & 54 deletions

File tree

Doc/library/stdtypes.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5927,6 +5927,15 @@ creation::
59275927
>>> type(l)
59285928
<class 'list'>
59295929

5930+
5931+
Instances of ``GenericAlias`` are not classes at runtime, even though they behave like classes (they can be instantiated and subclassed)::
5932+
5933+
>>> import inspect
5934+
>>> inspect.isclass(list[int])
5935+
False
5936+
5937+
This is true for :ref:`user-defined generics <user-defined-generics>` also.
5938+
59305939
Calling :func:`repr` or :func:`str` on a generic shows the parameterized type::
59315940

59325941
>>> repr(list[int])

Lib/test/test_typing.py

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

58495849
foo(42)
58505850

5851+
def test_genericalias_instance_isclass(self):
5852+
# test against user-defined generic classes
5853+
T = TypeVar('T')
5854+
5855+
class Node(Generic[T]):
5856+
def __init__(self, label: T,
5857+
left: 'Node[T] | None' = None,
5858+
right: 'Node[T] | None' = None):
5859+
self.label = label
5860+
self.left = left
5861+
self.right = right
5862+
5863+
self.assertTrue(inspect.isclass(Node))
5864+
self.assertFalse(inspect.isclass(Node[int]))
5865+
self.assertFalse(inspect.isclass(Node[str]))
5866+
5867+
# test against standard generic classes
5868+
self.assertFalse(inspect.isclass(set[int]))
5869+
self.assertFalse(inspect.isclass(list[bytes]))
5870+
self.assertFalse(inspect.isclass(dict[str, str]))
5871+
58515872
def test_implicit_any(self):
58525873
T = TypeVar('T')
58535874

Lib/typing.py

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,32 +1356,35 @@ def __dir__(self):
13561356

13571357

13581358
class _GenericAlias(_BaseGenericAlias, _root=True):
1359-
# The type of parameterized generics.
1360-
#
1361-
# That is, for example, `type(List[int])` is `_GenericAlias`.
1362-
#
1363-
# Objects which are instances of this class include:
1364-
# * Parameterized container types, e.g. `Tuple[int]`, `List[int]`.
1365-
# * Note that native container types, e.g. `tuple`, `list`, use
1366-
# `types.GenericAlias` instead.
1367-
# * Parameterized classes:
1368-
# class C[T]: pass
1369-
# # C[int] is a _GenericAlias
1370-
# * `Callable` aliases, generic `Callable` aliases, and
1371-
# parameterized `Callable` aliases:
1372-
# T = TypeVar('T')
1373-
# # _CallableGenericAlias inherits from _GenericAlias.
1374-
# A = Callable[[], None] # _CallableGenericAlias
1375-
# B = Callable[[T], None] # _CallableGenericAlias
1376-
# C = B[int] # _CallableGenericAlias
1377-
# * Parameterized `Final`, `ClassVar`, `TypeForm`, `TypeGuard`, and `TypeIs`:
1378-
# # All _GenericAlias
1379-
# Final[int]
1380-
# ClassVar[float]
1381-
# TypeForm[bytes]
1382-
# TypeGuard[bool]
1383-
# TypeIs[range]
1384-
1359+
"""The type of parameterized generics.
1360+
1361+
That is, for example, `type(List[int])` is `_GenericAlias`.
1362+
1363+
Objects which are instances of this class include:
1364+
* Parameterized container types, e.g. `Tuple[int]`, `List[int]`.
1365+
* Note that native container types, e.g. `tuple`, `list`, use
1366+
`types.GenericAlias` instead.
1367+
* Parameterized classes:
1368+
class C[T]: pass
1369+
# C[int] is a _GenericAlias
1370+
* `Callable` aliases, generic `Callable` aliases, and
1371+
parameterized `Callable` aliases:
1372+
T = TypeVar('T')
1373+
# _CallableGenericAlias inherits from _GenericAlias.
1374+
A = Callable[[], None] # _CallableGenericAlias
1375+
B = Callable[[T], None] # _CallableGenericAlias
1376+
C = B[int] # _CallableGenericAlias
1377+
* Parameterized `Final`, `ClassVar`, `TypeForm`, `TypeGuard`, and `TypeIs`:
1378+
# All _GenericAlias
1379+
Final[int]
1380+
ClassVar[float]
1381+
TypeForm[bytearray]
1382+
TypeGuard[bool]
1383+
TypeIs[range]
1384+
1385+
Note that instances of this class are not classes (e.g by `inspect.isclass`),
1386+
even though they behave like them.
1387+
"""
13851388
def __init__(self, origin, args, *, inst=True, name=None):
13861389
super().__init__(origin, inst=inst, name=name)
13871390
if not isinstance(args, tuple):
@@ -1413,20 +1416,21 @@ def __ror__(self, left):
14131416

14141417
@_tp_cache
14151418
def __getitem__(self, args):
1416-
# Parameterizes an already-parameterized object.
1417-
#
1418-
# For example, we arrive here doing something like:
1419-
# T1 = TypeVar('T1')
1420-
# T2 = TypeVar('T2')
1421-
# T3 = TypeVar('T3')
1422-
# class A(Generic[T1]): pass
1423-
# B = A[T2] # B is a _GenericAlias
1424-
# C = B[T3] # Invokes _GenericAlias.__getitem__
1425-
#
1426-
# We also arrive here when parameterizing a generic `Callable` alias:
1427-
# T = TypeVar('T')
1428-
# C = Callable[[T], None]
1429-
# C[int] # Invokes _GenericAlias.__getitem__
1419+
"""Parameterizes an already-parameterized object.
1420+
1421+
For example, we arrive here doing something like:
1422+
T1 = TypeVar('T1')
1423+
T2 = TypeVar('T2')
1424+
T3 = TypeVar('T3')
1425+
class A(Generic[T1]): pass
1426+
B = A[T2] # B is a _GenericAlias
1427+
C = B[T3] # Invokes _GenericAlias.__getitem__
1428+
1429+
We also arrive here when parameterizing a generic `Callable` alias:
1430+
T = TypeVar('T')
1431+
C = Callable[[T], None]
1432+
C[int] # Invokes _GenericAlias.__getitem__
1433+
"""
14301434

14311435
if self.__origin__ in (Generic, Protocol):
14321436
# Can't subscript Generic[...] or Protocol[...].
@@ -1443,20 +1447,20 @@ def __getitem__(self, args):
14431447
return r
14441448

14451449
def _determine_new_args(self, args):
1446-
# Determines new __args__ for __getitem__.
1447-
#
1448-
# For example, suppose we had:
1449-
# T1 = TypeVar('T1')
1450-
# T2 = TypeVar('T2')
1451-
# class A(Generic[T1, T2]): pass
1452-
# T3 = TypeVar('T3')
1453-
# B = A[int, T3]
1454-
# C = B[str]
1455-
# `B.__args__` is `(int, T3)`, so `C.__args__` should be `(int, str)`.
1456-
# Unfortunately, this is harder than it looks, because if `T3` is
1457-
# anything more exotic than a plain `TypeVar`, we need to consider
1458-
# edge cases.
1459-
1450+
"""Determines new __args__ for __getitem__.
1451+
1452+
For example, suppose we had:
1453+
T1 = TypeVar('T1')
1454+
T2 = TypeVar('T2')
1455+
class A(Generic[T1, T2]): pass
1456+
T3 = TypeVar('T3')
1457+
B = A[int, T3]
1458+
C = B[str]
1459+
`B.__args__` is `(int, T3)`, so `C.__args__` should be `(int, str)`.
1460+
Unfortunately, this is harder than it looks, because if `T3` is
1461+
anything more exotic than a plain `TypeVar`, we need to consider
1462+
edge cases.
1463+
"""
14601464
params = self.__parameters__
14611465
# In the example above, this would be {T3: str}
14621466
for param in params:

0 commit comments

Comments
 (0)