Skip to content

Commit a335d98

Browse files
authored
bpo-46589: Improve documentation for typing._GenericAlias (GH-31026)
(These should arguably be docstrings per convention in the rest of the file, but it doesn't really matter.)
1 parent 067c03b commit a335d98

File tree

1 file changed

+110
-32
lines changed

1 file changed

+110
-32
lines changed

Lib/typing.py

+110-32
Original file line numberDiff line numberDiff line change
@@ -1000,16 +1000,41 @@ def __dir__(self):
10001000

10011001

10021002
class _GenericAlias(_BaseGenericAlias, _root=True):
1003-
def __init__(self, origin, params, *, inst=True, name=None,
1003+
# The type of parameterized generics.
1004+
#
1005+
# That is, for example, `type(List[int])` is `_GenericAlias`.
1006+
#
1007+
# Objects which are instances of this class include:
1008+
# * Parameterized container types, e.g. `Tuple[int]`, `List[int]`.
1009+
# * Note that native container types, e.g. `tuple`, `list`, use
1010+
# `types.GenericAlias` instead.
1011+
# * Parameterized classes:
1012+
# T = TypeVar('T')
1013+
# class C(Generic[T]): pass
1014+
# # C[int] is a _GenericAlias
1015+
# * `Callable` aliases, generic `Callable` aliases, and
1016+
# parameterized `Callable` aliases:
1017+
# T = TypeVar('T')
1018+
# # _CallableGenericAlias inherits from _GenericAlias.
1019+
# A = Callable[[], None] # _CallableGenericAlias
1020+
# B = Callable[[T], None] # _CallableGenericAlias
1021+
# C = B[int] # _CallableGenericAlias
1022+
# * Parameterized `Final`, `ClassVar` and `TypeGuard`:
1023+
# # All _GenericAlias
1024+
# Final[int]
1025+
# ClassVar[float]
1026+
# TypeVar[bool]
1027+
1028+
def __init__(self, origin, args, *, inst=True, name=None,
10041029
_typevar_types=TypeVar,
10051030
_paramspec_tvars=False):
10061031
super().__init__(origin, inst=inst, name=name)
1007-
if not isinstance(params, tuple):
1008-
params = (params,)
1032+
if not isinstance(args, tuple):
1033+
args = (args,)
10091034
self.__args__ = tuple(... if a is _TypingEllipsis else
10101035
() if a is _TypingEmpty else
1011-
a for a in params)
1012-
self.__parameters__ = _collect_type_vars(params, typevar_types=_typevar_types)
1036+
a for a in args)
1037+
self.__parameters__ = _collect_type_vars(args, typevar_types=_typevar_types)
10131038
self._typevar_types = _typevar_types
10141039
self._paramspec_tvars = _paramspec_tvars
10151040
if not name:
@@ -1031,44 +1056,97 @@ def __ror__(self, left):
10311056
return Union[left, self]
10321057

10331058
@_tp_cache
1034-
def __getitem__(self, params):
1059+
def __getitem__(self, args):
1060+
# Parameterizes an already-parameterized object.
1061+
#
1062+
# For example, we arrive here doing something like:
1063+
# T1 = TypeVar('T1')
1064+
# T2 = TypeVar('T2')
1065+
# T3 = TypeVar('T3')
1066+
# class A(Generic[T1]): pass
1067+
# B = A[T2] # B is a _GenericAlias
1068+
# C = B[T3] # Invokes _GenericAlias.__getitem__
1069+
#
1070+
# We also arrive here when parameterizing a generic `Callable` alias:
1071+
# T = TypeVar('T')
1072+
# C = Callable[[T], None]
1073+
# C[int] # Invokes _GenericAlias.__getitem__
1074+
10351075
if self.__origin__ in (Generic, Protocol):
10361076
# Can't subscript Generic[...] or Protocol[...].
10371077
raise TypeError(f"Cannot subscript already-subscripted {self}")
1038-
if not isinstance(params, tuple):
1039-
params = (params,)
1040-
params = tuple(_type_convert(p) for p in params)
1078+
1079+
# Preprocess `args`.
1080+
if not isinstance(args, tuple):
1081+
args = (args,)
1082+
args = tuple(_type_convert(p) for p in args)
10411083
if (self._paramspec_tvars
10421084
and any(isinstance(t, ParamSpec) for t in self.__parameters__)):
1043-
params = _prepare_paramspec_params(self, params)
1085+
args = _prepare_paramspec_params(self, args)
10441086
else:
1045-
_check_generic(self, params, len(self.__parameters__))
1087+
_check_generic(self, args, len(self.__parameters__))
1088+
1089+
new_args = self._determine_new_args(args)
1090+
r = self.copy_with(new_args)
1091+
return r
1092+
1093+
def _determine_new_args(self, args):
1094+
# Determines new __args__ for __getitem__.
1095+
#
1096+
# For example, suppose we had:
1097+
# T1 = TypeVar('T1')
1098+
# T2 = TypeVar('T2')
1099+
# class A(Generic[T1, T2]): pass
1100+
# T3 = TypeVar('T3')
1101+
# B = A[int, T3]
1102+
# C = B[str]
1103+
# `B.__args__` is `(int, T3)`, so `C.__args__` should be `(int, str)`.
1104+
# Unfortunately, this is harder than it looks, because if `T3` is
1105+
# anything more exotic than a plain `TypeVar`, we need to consider
1106+
# edge cases.
1107+
1108+
# In the example above, this would be {T3: str}
1109+
new_arg_by_param = dict(zip(self.__parameters__, args))
10461110

1047-
subst = dict(zip(self.__parameters__, params))
10481111
new_args = []
1049-
for arg in self.__args__:
1050-
if isinstance(arg, self._typevar_types):
1051-
if isinstance(arg, ParamSpec):
1052-
arg = subst[arg]
1053-
if not _is_param_expr(arg):
1054-
raise TypeError(f"Expected a list of types, an ellipsis, "
1055-
f"ParamSpec, or Concatenate. Got {arg}")
1112+
for old_arg in self.__args__:
1113+
1114+
if isinstance(old_arg, ParamSpec):
1115+
new_arg = new_arg_by_param[old_arg]
1116+
if not _is_param_expr(new_arg):
1117+
raise TypeError(f"Expected a list of types, an ellipsis, "
1118+
f"ParamSpec, or Concatenate. Got {new_arg}")
1119+
elif isinstance(old_arg, self._typevar_types):
1120+
new_arg = new_arg_by_param[old_arg]
1121+
elif isinstance(old_arg, (_GenericAlias, GenericAlias, types.UnionType)):
1122+
subparams = old_arg.__parameters__
1123+
if not subparams:
1124+
new_arg = old_arg
10561125
else:
1057-
arg = subst[arg]
1058-
elif isinstance(arg, (_GenericAlias, GenericAlias, types.UnionType)):
1059-
subparams = arg.__parameters__
1060-
if subparams:
1061-
subargs = tuple(subst[x] for x in subparams)
1062-
arg = arg[subargs]
1063-
# Required to flatten out the args for CallableGenericAlias
1064-
if self.__origin__ == collections.abc.Callable and isinstance(arg, tuple):
1065-
new_args.extend(arg)
1126+
subargs = tuple(new_arg_by_param[x] for x in subparams)
1127+
new_arg = old_arg[subargs]
1128+
else:
1129+
new_arg = old_arg
1130+
1131+
if self.__origin__ == collections.abc.Callable and isinstance(new_arg, tuple):
1132+
# Consider the following `Callable`.
1133+
# C = Callable[[int], str]
1134+
# Here, `C.__args__` should be (int, str) - NOT ([int], str).
1135+
# That means that if we had something like...
1136+
# P = ParamSpec('P')
1137+
# T = TypeVar('T')
1138+
# C = Callable[P, T]
1139+
# D = C[[int, str], float]
1140+
# ...we need to be careful; `new_args` should end up as
1141+
# `(int, str, float)` rather than `([int, str], float)`.
1142+
new_args.extend(new_arg)
10661143
else:
1067-
new_args.append(arg)
1068-
return self.copy_with(tuple(new_args))
1144+
new_args.append(new_arg)
10691145

1070-
def copy_with(self, params):
1071-
return self.__class__(self.__origin__, params, name=self._name, inst=self._inst)
1146+
return tuple(new_args)
1147+
1148+
def copy_with(self, args):
1149+
return self.__class__(self.__origin__, args, name=self._name, inst=self._inst)
10721150

10731151
def __repr__(self):
10741152
if self._name:

0 commit comments

Comments
 (0)