mirror of
https://github.com/python/cpython.git
synced 2026-06-28 03:41:13 +00:00
[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:
parent
fc600e4178
commit
99dc88db09
3 changed files with 86 additions and 51 deletions
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
107
Lib/typing.py
107
Lib/typing.py
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue