mirror of
https://github.com/python/cpython.git
synced 2025-07-24 11:44:31 +00:00
bpo-44098: Drop ParamSpec from most `__parameters__
` in typing generics (GH-26013) (#26091)
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
(cherry picked from commit b2f3f8e3d8
)
This commit is contained in:
parent
0acdf255a5
commit
c55ff1b352
3 changed files with 59 additions and 14 deletions
|
@ -4359,6 +4359,31 @@ class ParamSpecTests(BaseTestCase):
|
|||
self.assertEqual(C1[int, str], Callable[[int], str])
|
||||
self.assertEqual(C1[[int, str, dict], float], Callable[[int, str, dict], float])
|
||||
|
||||
def test_no_paramspec_in__parameters__(self):
|
||||
# ParamSpec should not be found in __parameters__
|
||||
# of generics. Usages outside Callable, Concatenate
|
||||
# and Generic are invalid.
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
self.assertNotIn(P, List[P].__parameters__)
|
||||
self.assertIn(T, Tuple[T, P].__parameters__)
|
||||
|
||||
# Test for consistency with builtin generics.
|
||||
self.assertNotIn(P, list[P].__parameters__)
|
||||
self.assertIn(T, tuple[T, P].__parameters__)
|
||||
|
||||
def test_paramspec_in_nested_generics(self):
|
||||
# Although ParamSpec should not be found in __parameters__ of most
|
||||
# generics, they probably should be found when nested in
|
||||
# a valid location.
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
C1 = Callable[P, T]
|
||||
G1 = List[C1]
|
||||
G2 = list[C1]
|
||||
self.assertEqual(G1.__parameters__, (P, T))
|
||||
self.assertEqual(G2.__parameters__, (P, T))
|
||||
|
||||
|
||||
class ConcatenateTests(BaseTestCase):
|
||||
def test_basics(self):
|
||||
|
|
|
@ -195,15 +195,17 @@ def _type_repr(obj):
|
|||
return repr(obj)
|
||||
|
||||
|
||||
def _collect_type_vars(types):
|
||||
"""Collect all type variable-like variables contained
|
||||
def _collect_type_vars(types, typevar_types=None):
|
||||
"""Collect all type variable contained
|
||||
in types in order of first appearance (lexicographic order). For example::
|
||||
|
||||
_collect_type_vars((T, List[S, T])) == (T, S)
|
||||
"""
|
||||
if typevar_types is None:
|
||||
typevar_types = TypeVar
|
||||
tvars = []
|
||||
for t in types:
|
||||
if isinstance(t, _TypeVarLike) and t not in tvars:
|
||||
if isinstance(t, typevar_types) and t not in tvars:
|
||||
tvars.append(t)
|
||||
if isinstance(t, (_GenericAlias, GenericAlias)):
|
||||
tvars.extend([t for t in t.__parameters__ if t not in tvars])
|
||||
|
@ -932,7 +934,8 @@ class _BaseGenericAlias(_Final, _root=True):
|
|||
raise AttributeError(attr)
|
||||
|
||||
def __setattr__(self, attr, val):
|
||||
if _is_dunder(attr) or attr in ('_name', '_inst', '_nparams'):
|
||||
if _is_dunder(attr) or attr in {'_name', '_inst', '_nparams',
|
||||
'_typevar_types', '_paramspec_tvars'}:
|
||||
super().__setattr__(attr, val)
|
||||
else:
|
||||
setattr(self.__origin__, attr, val)
|
||||
|
@ -957,14 +960,18 @@ class _BaseGenericAlias(_Final, _root=True):
|
|||
|
||||
|
||||
class _GenericAlias(_BaseGenericAlias, _root=True):
|
||||
def __init__(self, origin, params, *, inst=True, name=None):
|
||||
def __init__(self, origin, params, *, inst=True, name=None,
|
||||
_typevar_types=TypeVar,
|
||||
_paramspec_tvars=False):
|
||||
super().__init__(origin, inst=inst, name=name)
|
||||
if not isinstance(params, tuple):
|
||||
params = (params,)
|
||||
self.__args__ = tuple(... if a is _TypingEllipsis else
|
||||
() if a is _TypingEmpty else
|
||||
a for a in params)
|
||||
self.__parameters__ = _collect_type_vars(params)
|
||||
self.__parameters__ = _collect_type_vars(params, typevar_types=_typevar_types)
|
||||
self._typevar_types = _typevar_types
|
||||
self._paramspec_tvars = _paramspec_tvars
|
||||
if not name:
|
||||
self.__module__ = origin.__module__
|
||||
|
||||
|
@ -991,14 +998,15 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
|
|||
if not isinstance(params, tuple):
|
||||
params = (params,)
|
||||
params = tuple(_type_convert(p) for p in params)
|
||||
if any(isinstance(t, ParamSpec) for t in self.__parameters__):
|
||||
params = _prepare_paramspec_params(self, params)
|
||||
if self._paramspec_tvars:
|
||||
if any(isinstance(t, ParamSpec) for t in self.__parameters__):
|
||||
params = _prepare_paramspec_params(self, params)
|
||||
_check_generic(self, params, len(self.__parameters__))
|
||||
|
||||
subst = dict(zip(self.__parameters__, params))
|
||||
new_args = []
|
||||
for arg in self.__args__:
|
||||
if isinstance(arg, _TypeVarLike):
|
||||
if isinstance(arg, self._typevar_types):
|
||||
arg = subst[arg]
|
||||
elif isinstance(arg, (_GenericAlias, GenericAlias)):
|
||||
subparams = arg.__parameters__
|
||||
|
@ -1115,7 +1123,9 @@ class _CallableGenericAlias(_GenericAlias, _root=True):
|
|||
class _CallableType(_SpecialGenericAlias, _root=True):
|
||||
def copy_with(self, params):
|
||||
return _CallableGenericAlias(self.__origin__, params,
|
||||
name=self._name, inst=self._inst)
|
||||
name=self._name, inst=self._inst,
|
||||
_typevar_types=(TypeVar, ParamSpec),
|
||||
_paramspec_tvars=True)
|
||||
|
||||
def __getitem__(self, params):
|
||||
if not isinstance(params, tuple) or len(params) != 2:
|
||||
|
@ -1208,7 +1218,10 @@ class _LiteralGenericAlias(_GenericAlias, _root=True):
|
|||
|
||||
|
||||
class _ConcatenateGenericAlias(_GenericAlias, _root=True):
|
||||
pass
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs,
|
||||
_typevar_types=(TypeVar, ParamSpec),
|
||||
_paramspec_tvars=True)
|
||||
|
||||
|
||||
class Generic:
|
||||
|
@ -1244,7 +1257,7 @@ class Generic:
|
|||
params = tuple(_type_convert(p) for p in params)
|
||||
if cls in (Generic, Protocol):
|
||||
# Generic and Protocol can only be subscripted with unique type variables.
|
||||
if not all(isinstance(p, _TypeVarLike) for p in params):
|
||||
if not all(isinstance(p, (TypeVar, ParamSpec)) for p in params):
|
||||
raise TypeError(
|
||||
f"Parameters to {cls.__name__}[...] must all be type variables "
|
||||
f"or parameter specification variables.")
|
||||
|
@ -1256,7 +1269,9 @@ class Generic:
|
|||
if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
|
||||
params = _prepare_paramspec_params(cls, params)
|
||||
_check_generic(cls, params, len(cls.__parameters__))
|
||||
return _GenericAlias(cls, params)
|
||||
return _GenericAlias(cls, params,
|
||||
_typevar_types=(TypeVar, ParamSpec),
|
||||
_paramspec_tvars=True)
|
||||
|
||||
def __init_subclass__(cls, *args, **kwargs):
|
||||
super().__init_subclass__(*args, **kwargs)
|
||||
|
@ -1268,7 +1283,7 @@ class Generic:
|
|||
if error:
|
||||
raise TypeError("Cannot inherit from plain Generic")
|
||||
if '__orig_bases__' in cls.__dict__:
|
||||
tvars = _collect_type_vars(cls.__orig_bases__)
|
||||
tvars = _collect_type_vars(cls.__orig_bases__, (TypeVar, ParamSpec))
|
||||
# Look for Generic[T1, ..., Tn].
|
||||
# If found, tvars must be a subset of it.
|
||||
# If not found, tvars is it.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
``typing.ParamSpec`` will no longer be found in the ``__parameters__`` of
|
||||
most :mod:`typing` generics except in valid use locations specified by
|
||||
:pep:`612`. This prevents incorrect usage like ``typing.List[P][int]``. This
|
||||
change means incorrect usage which may have passed silently in 3.10 beta 1
|
||||
and earlier will now error.
|
Loading…
Add table
Add a link
Reference in a new issue