Skip to content

gh-132467: Add test case to check whether GenericAlias instance is not class, update docstring of typing._GenericAlias #133504

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5346,6 +5346,15 @@ creation::
>>> type(l)
<class 'list'>


Additionally, instances of ``GenericAlias`` are not considered to be classes at runtime, even though they behave like classes (e.g. they can be instantiated)::

>>> 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])
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -5638,6 +5638,21 @@ def foo(x: T):

foo(42)

def test_genericalias_isclass(self):
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]))

def test_implicit_any(self):
T = TypeVar('T')

Expand Down
110 changes: 57 additions & 53 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1283,31 +1283,34 @@ 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`, `TypeGuard`, and `TypeIs`:
# All _GenericAlias
Final[int]
ClassVar[float]
TypeGuard[bool]
TypeIs[range]

Note that objects of this class is not considered to be a class (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):
Expand Down Expand Up @@ -1339,20 +1342,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[...].
Expand All @@ -1369,20 +1373,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:
Expand Down
Loading