Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b2f3f8e

Browse filesBrowse files
bpo-44098: Drop ParamSpec from most __parameters__ in typing generics (GH-26013)
Added two new attributes to ``_GenericAlias``: * ``_typevar_types``, a single type or tuple of types indicating what types are treated as a ``TypeVar``. Used for ``isinstance`` checks. * ``_paramspec_tvars ``, a boolean flag which guards special behavior for dealing with ``ParamSpec``. Setting it to ``True`` means this class deals with ``ParamSpec``. Automerge-Triggered-By: GH:gvanrossum
1 parent 7565586 commit b2f3f8e
Copy full SHA for b2f3f8e

File tree

3 files changed

+59
-14
lines changed
Filter options

3 files changed

+59
-14
lines changed

‎Lib/test/test_typing.py

Copy file name to clipboardExpand all lines: Lib/test/test_typing.py
+25Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4359,6 +4359,31 @@ def test_var_substitution(self):
43594359
self.assertEqual(C1[int, str], Callable[[int], str])
43604360
self.assertEqual(C1[[int, str, dict], float], Callable[[int, str, dict], float])
43614361

4362+
def test_no_paramspec_in__parameters__(self):
4363+
# ParamSpec should not be found in __parameters__
4364+
# of generics. Usages outside Callable, Concatenate
4365+
# and Generic are invalid.
4366+
T = TypeVar("T")
4367+
P = ParamSpec("P")
4368+
self.assertNotIn(P, List[P].__parameters__)
4369+
self.assertIn(T, Tuple[T, P].__parameters__)
4370+
4371+
# Test for consistency with builtin generics.
4372+
self.assertNotIn(P, list[P].__parameters__)
4373+
self.assertIn(T, tuple[T, P].__parameters__)
4374+
4375+
def test_paramspec_in_nested_generics(self):
4376+
# Although ParamSpec should not be found in __parameters__ of most
4377+
# generics, they probably should be found when nested in
4378+
# a valid location.
4379+
T = TypeVar("T")
4380+
P = ParamSpec("P")
4381+
C1 = Callable[P, T]
4382+
G1 = List[C1]
4383+
G2 = list[C1]
4384+
self.assertEqual(G1.__parameters__, (P, T))
4385+
self.assertEqual(G2.__parameters__, (P, T))
4386+
43624387

43634388
class ConcatenateTests(BaseTestCase):
43644389
def test_basics(self):

‎Lib/typing.py

Copy file name to clipboardExpand all lines: Lib/typing.py
+29-14Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,17 @@ def _type_repr(obj):
195195
return repr(obj)
196196

197197

198-
def _collect_type_vars(types):
199-
"""Collect all type variable-like variables contained
198+
def _collect_type_vars(types, typevar_types=None):
199+
"""Collect all type variable contained
200200
in types in order of first appearance (lexicographic order). For example::
201201
202202
_collect_type_vars((T, List[S, T])) == (T, S)
203203
"""
204+
if typevar_types is None:
205+
typevar_types = TypeVar
204206
tvars = []
205207
for t in types:
206-
if isinstance(t, _TypeVarLike) and t not in tvars:
208+
if isinstance(t, typevar_types) and t not in tvars:
207209
tvars.append(t)
208210
if isinstance(t, (_GenericAlias, GenericAlias)):
209211
tvars.extend([t for t in t.__parameters__ if t not in tvars])
@@ -932,7 +934,8 @@ def __getattr__(self, attr):
932934
raise AttributeError(attr)
933935

934936
def __setattr__(self, attr, val):
935-
if _is_dunder(attr) or attr in ('_name', '_inst', '_nparams'):
937+
if _is_dunder(attr) or attr in {'_name', '_inst', '_nparams',
938+
'_typevar_types', '_paramspec_tvars'}:
936939
super().__setattr__(attr, val)
937940
else:
938941
setattr(self.__origin__, attr, val)
@@ -957,14 +960,18 @@ def __subclasscheck__(self, cls):
957960

958961

959962
class _GenericAlias(_BaseGenericAlias, _root=True):
960-
def __init__(self, origin, params, *, inst=True, name=None):
963+
def __init__(self, origin, params, *, inst=True, name=None,
964+
_typevar_types=TypeVar,
965+
_paramspec_tvars=False):
961966
super().__init__(origin, inst=inst, name=name)
962967
if not isinstance(params, tuple):
963968
params = (params,)
964969
self.__args__ = tuple(... if a is _TypingEllipsis else
965970
() if a is _TypingEmpty else
966971
a for a in params)
967-
self.__parameters__ = _collect_type_vars(params)
972+
self.__parameters__ = _collect_type_vars(params, typevar_types=_typevar_types)
973+
self._typevar_types = _typevar_types
974+
self._paramspec_tvars = _paramspec_tvars
968975
if not name:
969976
self.__module__ = origin.__module__
970977

@@ -991,14 +998,15 @@ def __getitem__(self, params):
991998
if not isinstance(params, tuple):
992999
params = (params,)
9931000
params = tuple(_type_convert(p) for p in params)
994-
if any(isinstance(t, ParamSpec) for t in self.__parameters__):
995-
params = _prepare_paramspec_params(self, params)
1001+
if self._paramspec_tvars:
1002+
if any(isinstance(t, ParamSpec) for t in self.__parameters__):
1003+
params = _prepare_paramspec_params(self, params)
9961004
_check_generic(self, params, len(self.__parameters__))
9971005

9981006
subst = dict(zip(self.__parameters__, params))
9991007
new_args = []
10001008
for arg in self.__args__:
1001-
if isinstance(arg, _TypeVarLike):
1009+
if isinstance(arg, self._typevar_types):
10021010
arg = subst[arg]
10031011
elif isinstance(arg, (_GenericAlias, GenericAlias)):
10041012
subparams = arg.__parameters__
@@ -1115,7 +1123,9 @@ def __reduce__(self):
11151123
class _CallableType(_SpecialGenericAlias, _root=True):
11161124
def copy_with(self, params):
11171125
return _CallableGenericAlias(self.__origin__, params,
1118-
name=self._name, inst=self._inst)
1126+
name=self._name, inst=self._inst,
1127+
_typevar_types=(TypeVar, ParamSpec),
1128+
_paramspec_tvars=True)
11191129

11201130
def __getitem__(self, params):
11211131
if not isinstance(params, tuple) or len(params) != 2:
@@ -1208,7 +1218,10 @@ def __hash__(self):
12081218

12091219

12101220
class _ConcatenateGenericAlias(_GenericAlias, _root=True):
1211-
pass
1221+
def __init__(self, *args, **kwargs):
1222+
super().__init__(*args, **kwargs,
1223+
_typevar_types=(TypeVar, ParamSpec),
1224+
_paramspec_tvars=True)
12121225

12131226

12141227
class Generic:
@@ -1244,7 +1257,7 @@ def __class_getitem__(cls, params):
12441257
params = tuple(_type_convert(p) for p in params)
12451258
if cls in (Generic, Protocol):
12461259
# Generic and Protocol can only be subscripted with unique type variables.
1247-
if not all(isinstance(p, _TypeVarLike) for p in params):
1260+
if not all(isinstance(p, (TypeVar, ParamSpec)) for p in params):
12481261
raise TypeError(
12491262
f"Parameters to {cls.__name__}[...] must all be type variables "
12501263
f"or parameter specification variables.")
@@ -1256,7 +1269,9 @@ def __class_getitem__(cls, params):
12561269
if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
12571270
params = _prepare_paramspec_params(cls, params)
12581271
_check_generic(cls, params, len(cls.__parameters__))
1259-
return _GenericAlias(cls, params)
1272+
return _GenericAlias(cls, params,
1273+
_typevar_types=(TypeVar, ParamSpec),
1274+
_paramspec_tvars=True)
12601275

12611276
def __init_subclass__(cls, *args, **kwargs):
12621277
super().__init_subclass__(*args, **kwargs)
@@ -1268,7 +1283,7 @@ def __init_subclass__(cls, *args, **kwargs):
12681283
if error:
12691284
raise TypeError("Cannot inherit from plain Generic")
12701285
if '__orig_bases__' in cls.__dict__:
1271-
tvars = _collect_type_vars(cls.__orig_bases__)
1286+
tvars = _collect_type_vars(cls.__orig_bases__, (TypeVar, ParamSpec))
12721287
# Look for Generic[T1, ..., Tn].
12731288
# If found, tvars must be a subset of it.
12741289
# If not found, tvars is it.
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``typing.ParamSpec`` will no longer be found in the ``__parameters__`` of
2+
most :mod:`typing` generics except in valid use locations specified by
3+
:pep:`612`. This prevents incorrect usage like ``typing.List[P][int]``. This
4+
change means incorrect usage which may have passed silently in 3.10 beta 1
5+
and earlier will now error.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.