Skip to content

Commit e1dbafb

Browse files
[3.14] 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 a8003f6 commit e1dbafb

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
@@ -5870,6 +5870,15 @@ creation::
58705870
>>> type(l)
58715871
<class 'list'>
58725872

5873+
5874+
Instances of ``GenericAlias`` are not classes at runtime, even though they behave like classes (they can be instantiated and subclassed)::
5875+
5876+
>>> import inspect
5877+
>>> inspect.isclass(list[int])
5878+
False
5879+
5880+
This is true for :ref:`user-defined generics <user-defined-generics>` also.
5881+
58735882
Calling :func:`repr` or :func:`str` on a generic shows the parameterized type::
58745883

58755884
>>> repr(list[int])

Lib/test/test_typing.py

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

56865686
foo(42)
56875687

5688+
def test_genericalias_instance_isclass(self):
5689+
# test against user-defined generic classes
5690+
T = TypeVar('T')
5691+
5692+
class Node(Generic[T]):
5693+
def __init__(self, label: T,
5694+
left: 'Node[T] | None' = None,
5695+
right: 'Node[T] | None' = None):
5696+
self.label = label
5697+
self.left = left
5698+
self.right = right
5699+
5700+
self.assertTrue(inspect.isclass(Node))
5701+
self.assertFalse(inspect.isclass(Node[int]))
5702+
self.assertFalse(inspect.isclass(Node[str]))
5703+
5704+
# test against standard generic classes
5705+
self.assertFalse(inspect.isclass(set[int]))
5706+
self.assertFalse(inspect.isclass(list[bytes]))
5707+
self.assertFalse(inspect.isclass(dict[str, str]))
5708+
56885709
def test_implicit_any(self):
56895710
T = TypeVar('T')
56905711

Lib/typing.py

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

13141314

13151315
class _GenericAlias(_BaseGenericAlias, _root=True):
1316-
# The type of parameterized generics.
1317-
#
1318-
# That is, for example, `type(List[int])` is `_GenericAlias`.
1319-
#
1320-
# Objects which are instances of this class include:
1321-
# * Parameterized container types, e.g. `Tuple[int]`, `List[int]`.
1322-
# * Note that native container types, e.g. `tuple`, `list`, use
1323-
# `types.GenericAlias` instead.
1324-
# * Parameterized classes:
1325-
# class C[T]: pass
1326-
# # C[int] is a _GenericAlias
1327-
# * `Callable` aliases, generic `Callable` aliases, and
1328-
# parameterized `Callable` aliases:
1329-
# T = TypeVar('T')
1330-
# # _CallableGenericAlias inherits from _GenericAlias.
1331-
# A = Callable[[], None] # _CallableGenericAlias
1332-
# B = Callable[[T], None] # _CallableGenericAlias
1333-
# C = B[int] # _CallableGenericAlias
1334-
# * Parameterized `Final`, `ClassVar`, `TypeGuard`, and `TypeIs`:
1335-
# # All _GenericAlias
1336-
# Final[int]
1337-
# ClassVar[float]
1338-
# TypeGuard[bool]
1339-
# TypeIs[range]
1340-
1316+
"""The type of parameterized generics.
1317+
1318+
That is, for example, `type(List[int])` is `_GenericAlias`.
1319+
1320+
Objects which are instances of this class include:
1321+
* Parameterized container types, e.g. `Tuple[int]`, `List[int]`.
1322+
* Note that native container types, e.g. `tuple`, `list`, use
1323+
`types.GenericAlias` instead.
1324+
* Parameterized classes:
1325+
class C[T]: pass
1326+
# C[int] is a _GenericAlias
1327+
* `Callable` aliases, generic `Callable` aliases, and
1328+
parameterized `Callable` aliases:
1329+
T = TypeVar('T')
1330+
# _CallableGenericAlias inherits from _GenericAlias.
1331+
A = Callable[[], None] # _CallableGenericAlias
1332+
B = Callable[[T], None] # _CallableGenericAlias
1333+
C = B[int] # _CallableGenericAlias
1334+
* Parameterized `Final`, `ClassVar`, `TypeForm`, `TypeGuard`, and `TypeIs`:
1335+
# All _GenericAlias
1336+
Final[int]
1337+
ClassVar[float]
1338+
TypeForm[bytearray]
1339+
TypeGuard[bool]
1340+
TypeIs[range]
1341+
1342+
Note that instances of this class are not classes (e.g by `inspect.isclass`),
1343+
even though they behave like them.
1344+
"""
13411345
def __init__(self, origin, args, *, inst=True, name=None):
13421346
super().__init__(origin, inst=inst, name=name)
13431347
if not isinstance(args, tuple):
@@ -1369,20 +1373,21 @@ def __ror__(self, left):
13691373

13701374
@_tp_cache
13711375
def __getitem__(self, args):
1372-
# Parameterizes an already-parameterized object.
1373-
#
1374-
# For example, we arrive here doing something like:
1375-
# T1 = TypeVar('T1')
1376-
# T2 = TypeVar('T2')
1377-
# T3 = TypeVar('T3')
1378-
# class A(Generic[T1]): pass
1379-
# B = A[T2] # B is a _GenericAlias
1380-
# C = B[T3] # Invokes _GenericAlias.__getitem__
1381-
#
1382-
# We also arrive here when parameterizing a generic `Callable` alias:
1383-
# T = TypeVar('T')
1384-
# C = Callable[[T], None]
1385-
# C[int] # Invokes _GenericAlias.__getitem__
1376+
"""Parameterizes an already-parameterized object.
1377+
1378+
For example, we arrive here doing something like:
1379+
T1 = TypeVar('T1')
1380+
T2 = TypeVar('T2')
1381+
T3 = TypeVar('T3')
1382+
class A(Generic[T1]): pass
1383+
B = A[T2] # B is a _GenericAlias
1384+
C = B[T3] # Invokes _GenericAlias.__getitem__
1385+
1386+
We also arrive here when parameterizing a generic `Callable` alias:
1387+
T = TypeVar('T')
1388+
C = Callable[[T], None]
1389+
C[int] # Invokes _GenericAlias.__getitem__
1390+
"""
13861391

13871392
if self.__origin__ in (Generic, Protocol):
13881393
# Can't subscript Generic[...] or Protocol[...].
@@ -1399,20 +1404,20 @@ def __getitem__(self, args):
13991404
return r
14001405

14011406
def _determine_new_args(self, args):
1402-
# Determines new __args__ for __getitem__.
1403-
#
1404-
# For example, suppose we had:
1405-
# T1 = TypeVar('T1')
1406-
# T2 = TypeVar('T2')
1407-
# class A(Generic[T1, T2]): pass
1408-
# T3 = TypeVar('T3')
1409-
# B = A[int, T3]
1410-
# C = B[str]
1411-
# `B.__args__` is `(int, T3)`, so `C.__args__` should be `(int, str)`.
1412-
# Unfortunately, this is harder than it looks, because if `T3` is
1413-
# anything more exotic than a plain `TypeVar`, we need to consider
1414-
# edge cases.
1415-
1407+
"""Determines new __args__ for __getitem__.
1408+
1409+
For example, suppose we had:
1410+
T1 = TypeVar('T1')
1411+
T2 = TypeVar('T2')
1412+
class A(Generic[T1, T2]): pass
1413+
T3 = TypeVar('T3')
1414+
B = A[int, T3]
1415+
C = B[str]
1416+
`B.__args__` is `(int, T3)`, so `C.__args__` should be `(int, str)`.
1417+
Unfortunately, this is harder than it looks, because if `T3` is
1418+
anything more exotic than a plain `TypeVar`, we need to consider
1419+
edge cases.
1420+
"""
14161421
params = self.__parameters__
14171422
# In the example above, this would be {T3: str}
14181423
for param in params:

0 commit comments

Comments
 (0)