bpo-41559: Implement PEP 612 - Add ParamSpec and Concatenate to typing (#23702)
This commit is contained in:
parent
cc3467a57b
commit
73607be686
|
@ -416,7 +416,7 @@ class Collection(Sized, Iterable, Container):
|
|||
class _CallableGenericAlias(GenericAlias):
|
||||
""" Represent `Callable[argtypes, resulttype]`.
|
||||
|
||||
This sets ``__args__`` to a tuple containing the flattened``argtypes``
|
||||
This sets ``__args__`` to a tuple containing the flattened ``argtypes``
|
||||
followed by ``resulttype``.
|
||||
|
||||
Example: ``Callable[[int, str], float]`` sets ``__args__`` to
|
||||
|
@ -444,7 +444,7 @@ class _CallableGenericAlias(GenericAlias):
|
|||
return super().__new__(cls, origin, ga_args)
|
||||
|
||||
def __repr__(self):
|
||||
if len(self.__args__) == 2 and self.__args__[0] is Ellipsis:
|
||||
if _has_special_args(self.__args__):
|
||||
return super().__repr__()
|
||||
return (f'collections.abc.Callable'
|
||||
f'[[{", ".join([_type_repr(a) for a in self.__args__[:-1]])}], '
|
||||
|
@ -452,7 +452,7 @@ class _CallableGenericAlias(GenericAlias):
|
|||
|
||||
def __reduce__(self):
|
||||
args = self.__args__
|
||||
if not (len(args) == 2 and args[0] is Ellipsis):
|
||||
if not _has_special_args(args):
|
||||
args = list(args[:-1]), args[-1]
|
||||
return _CallableGenericAlias, (Callable, args)
|
||||
|
||||
|
@ -461,12 +461,28 @@ class _CallableGenericAlias(GenericAlias):
|
|||
# rather than the default types.GenericAlias object.
|
||||
ga = super().__getitem__(item)
|
||||
args = ga.__args__
|
||||
t_result = args[-1]
|
||||
t_args = args[:-1]
|
||||
args = (t_args, t_result)
|
||||
# args[0] occurs due to things like Z[[int, str, bool]] from PEP 612
|
||||
if not isinstance(ga.__args__[0], tuple):
|
||||
t_result = ga.__args__[-1]
|
||||
t_args = ga.__args__[:-1]
|
||||
args = (t_args, t_result)
|
||||
return _CallableGenericAlias(Callable, args)
|
||||
|
||||
|
||||
def _has_special_args(args):
|
||||
"""Checks if args[0] matches either ``...``, ``ParamSpec`` or
|
||||
``_ConcatenateGenericAlias`` from typing.py
|
||||
"""
|
||||
if len(args) != 2:
|
||||
return False
|
||||
obj = args[0]
|
||||
if obj is Ellipsis:
|
||||
return True
|
||||
obj = type(obj)
|
||||
names = ('ParamSpec', '_ConcatenateGenericAlias')
|
||||
return obj.__module__ == 'typing' and any(obj.__name__ == name for name in names)
|
||||
|
||||
|
||||
def _type_repr(obj):
|
||||
"""Return the repr() of an object, special-casing types (internal helper).
|
||||
|
||||
|
|
|
@ -369,6 +369,27 @@ class BaseTest(unittest.TestCase):
|
|||
self.assertEqual(c1.__args__, c2.__args__)
|
||||
self.assertEqual(hash(c1.__args__), hash(c2.__args__))
|
||||
|
||||
with self.subTest("Testing ParamSpec uses"):
|
||||
P = typing.ParamSpec('P')
|
||||
C1 = Callable[P, T]
|
||||
# substitution
|
||||
self.assertEqual(C1[int, str], Callable[[int], str])
|
||||
self.assertEqual(C1[[int, str], str], Callable[[int, str], str])
|
||||
self.assertEqual(repr(C1).split(".")[-1], "Callable[~P, ~T]")
|
||||
self.assertEqual(repr(C1[int, str]).split(".")[-1], "Callable[[int], str]")
|
||||
|
||||
C2 = Callable[P, int]
|
||||
# special case in PEP 612 where
|
||||
# X[int, str, float] == X[[int, str, float]]
|
||||
self.assertEqual(C2[int, str, float], C2[[int, str, float]])
|
||||
self.assertEqual(repr(C2).split(".")[-1], "Callable[~P, int]")
|
||||
self.assertEqual(repr(C2[int, str]).split(".")[-1], "Callable[[int, str], int]")
|
||||
|
||||
with self.subTest("Testing Concatenate uses"):
|
||||
P = typing.ParamSpec('P')
|
||||
C1 = Callable[typing.Concatenate[int, P], int]
|
||||
self.assertEqual(repr(C1), "collections.abc.Callable"
|
||||
"[typing.Concatenate[int, ~P], int]")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -25,6 +25,7 @@ from typing import IO, TextIO, BinaryIO
|
|||
from typing import Pattern, Match
|
||||
from typing import Annotated, ForwardRef
|
||||
from typing import TypeAlias
|
||||
from typing import ParamSpec, Concatenate
|
||||
import abc
|
||||
import typing
|
||||
import weakref
|
||||
|
@ -1130,10 +1131,6 @@ class ProtocolTests(BaseTestCase):
|
|||
PR[int]
|
||||
with self.assertRaises(TypeError):
|
||||
P[int, str]
|
||||
with self.assertRaises(TypeError):
|
||||
PR[int, 1]
|
||||
with self.assertRaises(TypeError):
|
||||
PR[int, ClassVar]
|
||||
|
||||
class C(PR[int, T]): pass
|
||||
|
||||
|
@ -1155,8 +1152,6 @@ class ProtocolTests(BaseTestCase):
|
|||
self.assertIsSubclass(P, PR)
|
||||
with self.assertRaises(TypeError):
|
||||
PR[int]
|
||||
with self.assertRaises(TypeError):
|
||||
PR[int, 1]
|
||||
|
||||
class P1(Protocol, Generic[T]):
|
||||
def bar(self, x: T) -> str: ...
|
||||
|
@ -1175,8 +1170,6 @@ class ProtocolTests(BaseTestCase):
|
|||
return x
|
||||
|
||||
self.assertIsInstance(Test(), PSub)
|
||||
with self.assertRaises(TypeError):
|
||||
PR[int, ClassVar]
|
||||
|
||||
def test_init_called(self):
|
||||
T = TypeVar('T')
|
||||
|
@ -1746,8 +1739,6 @@ class GenericTests(BaseTestCase):
|
|||
self.assertEqual(typing.Iterable[Tuple[T, T]][T], typing.Iterable[Tuple[T, T]])
|
||||
with self.assertRaises(TypeError):
|
||||
Tuple[T, int][()]
|
||||
with self.assertRaises(TypeError):
|
||||
Tuple[T, U][T, ...]
|
||||
|
||||
self.assertEqual(Union[T, int][int], int)
|
||||
self.assertEqual(Union[T, U][int, Union[int, str]], Union[int, str])
|
||||
|
@ -1759,10 +1750,6 @@ class GenericTests(BaseTestCase):
|
|||
|
||||
self.assertEqual(Callable[[T], T][KT], Callable[[KT], KT])
|
||||
self.assertEqual(Callable[..., List[T]][int], Callable[..., List[int]])
|
||||
with self.assertRaises(TypeError):
|
||||
Callable[[T], U][..., int]
|
||||
with self.assertRaises(TypeError):
|
||||
Callable[[T], U][[], int]
|
||||
|
||||
def test_extended_generic_rules_repr(self):
|
||||
T = TypeVar('T')
|
||||
|
@ -4243,6 +4230,111 @@ class TypeAliasTests(BaseTestCase):
|
|||
TypeAlias[int]
|
||||
|
||||
|
||||
class ParamSpecTests(BaseTestCase):
|
||||
|
||||
def test_basic_plain(self):
|
||||
P = ParamSpec('P')
|
||||
self.assertEqual(P, P)
|
||||
self.assertIsInstance(P, ParamSpec)
|
||||
|
||||
def test_valid_uses(self):
|
||||
P = ParamSpec('P')
|
||||
T = TypeVar('T')
|
||||
C1 = Callable[P, int]
|
||||
self.assertEqual(C1.__args__, (P, int))
|
||||
self.assertEqual(C1.__parameters__, (P,))
|
||||
C2 = Callable[P, T]
|
||||
self.assertEqual(C2.__args__, (P, T))
|
||||
self.assertEqual(C2.__parameters__, (P, T))
|
||||
# Test collections.abc.Callable too.
|
||||
C3 = collections.abc.Callable[P, int]
|
||||
self.assertEqual(C3.__args__, (P, int))
|
||||
self.assertEqual(C3.__parameters__, (P,))
|
||||
C4 = collections.abc.Callable[P, T]
|
||||
self.assertEqual(C4.__args__, (P, T))
|
||||
self.assertEqual(C4.__parameters__, (P, T))
|
||||
|
||||
# ParamSpec instances should also have args and kwargs attributes.
|
||||
self.assertIn('args', dir(P))
|
||||
self.assertIn('kwargs', dir(P))
|
||||
P.args
|
||||
P.kwargs
|
||||
|
||||
def test_user_generics(self):
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
P_2 = ParamSpec("P_2")
|
||||
|
||||
class X(Generic[T, P]):
|
||||
f: Callable[P, int]
|
||||
x: T
|
||||
G1 = X[int, P_2]
|
||||
self.assertEqual(G1.__args__, (int, P_2))
|
||||
self.assertEqual(G1.__parameters__, (P_2,))
|
||||
|
||||
G2 = X[int, Concatenate[int, P_2]]
|
||||
self.assertEqual(G2.__args__, (int, Concatenate[int, P_2]))
|
||||
self.assertEqual(G2.__parameters__, (P_2,))
|
||||
|
||||
G3 = X[int, [int, bool]]
|
||||
self.assertEqual(G3.__args__, (int, (int, bool)))
|
||||
self.assertEqual(G3.__parameters__, ())
|
||||
|
||||
G4 = X[int, ...]
|
||||
self.assertEqual(G4.__args__, (int, Ellipsis))
|
||||
self.assertEqual(G4.__parameters__, ())
|
||||
|
||||
class Z(Generic[P]):
|
||||
f: Callable[P, int]
|
||||
|
||||
G5 = Z[[int, str, bool]]
|
||||
self.assertEqual(G5.__args__, ((int, str, bool),))
|
||||
self.assertEqual(G5.__parameters__, ())
|
||||
|
||||
G6 = Z[int, str, bool]
|
||||
self.assertEqual(G6.__args__, ((int, str, bool),))
|
||||
self.assertEqual(G6.__parameters__, ())
|
||||
|
||||
# G5 and G6 should be equivalent according to the PEP
|
||||
self.assertEqual(G5.__args__, G6.__args__)
|
||||
self.assertEqual(G5.__origin__, G6.__origin__)
|
||||
self.assertEqual(G5.__parameters__, G6.__parameters__)
|
||||
self.assertEqual(G5, G6)
|
||||
|
||||
def test_var_substitution(self):
|
||||
T = TypeVar("T")
|
||||
P = ParamSpec("P")
|
||||
C1 = Callable[P, T]
|
||||
self.assertEqual(C1[int, str], Callable[[int], str])
|
||||
self.assertEqual(C1[[int, str, dict], float], Callable[[int, str, dict], float])
|
||||
|
||||
|
||||
class ConcatenateTests(BaseTestCase):
|
||||
def test_basics(self):
|
||||
P = ParamSpec('P')
|
||||
class MyClass: ...
|
||||
c = Concatenate[MyClass, P]
|
||||
self.assertNotEqual(c, Concatenate)
|
||||
|
||||
def test_valid_uses(self):
|
||||
P = ParamSpec('P')
|
||||
T = TypeVar('T')
|
||||
C1 = Callable[Concatenate[int, P], int]
|
||||
self.assertEqual(C1.__args__, (Concatenate[int, P], int))
|
||||
self.assertEqual(C1.__parameters__, (P,))
|
||||
C2 = Callable[Concatenate[int, T, P], T]
|
||||
self.assertEqual(C2.__args__, (Concatenate[int, T, P], T))
|
||||
self.assertEqual(C2.__parameters__, (T, P))
|
||||
|
||||
# Test collections.abc.Callable too.
|
||||
C3 = collections.abc.Callable[Concatenate[int, P], int]
|
||||
self.assertEqual(C3.__args__, (Concatenate[int, P], int))
|
||||
self.assertEqual(C3.__parameters__, (P,))
|
||||
C4 = collections.abc.Callable[Concatenate[int, T, P], T]
|
||||
self.assertEqual(C4.__args__, (Concatenate[int, T, P], T))
|
||||
self.assertEqual(C4.__parameters__, (T, P))
|
||||
|
||||
|
||||
class AllTests(BaseTestCase):
|
||||
"""Tests for __all__."""
|
||||
|
||||
|
|
209
Lib/typing.py
209
Lib/typing.py
|
@ -4,8 +4,10 @@ The typing module: Support for gradual typing as defined by PEP 484.
|
|||
At large scale, the structure of the module is following:
|
||||
* Imports and exports, all public names should be explicitly added to __all__.
|
||||
* Internal helper functions: these should never be used in code outside this module.
|
||||
* _SpecialForm and its instances (special forms): Any, NoReturn, ClassVar, Union, Optional
|
||||
* Two classes whose instances can be type arguments in addition to types: ForwardRef and TypeVar
|
||||
* _SpecialForm and its instances (special forms):
|
||||
Any, NoReturn, ClassVar, Union, Optional, Concatenate
|
||||
* Classes whose instances can be type arguments in addition to types:
|
||||
ForwardRef, TypeVar and ParamSpec
|
||||
* The core of internal generics API: _GenericAlias and _VariadicGenericAlias, the latter is
|
||||
currently only used by Tuple and Callable. All subscripted types like X[int], Union[int, str],
|
||||
etc., are instances of either of these classes.
|
||||
|
@ -36,11 +38,13 @@ __all__ = [
|
|||
'Any',
|
||||
'Callable',
|
||||
'ClassVar',
|
||||
'Concatenate',
|
||||
'Final',
|
||||
'ForwardRef',
|
||||
'Generic',
|
||||
'Literal',
|
||||
'Optional',
|
||||
'ParamSpec',
|
||||
'Protocol',
|
||||
'Tuple',
|
||||
'Type',
|
||||
|
@ -154,7 +158,7 @@ def _type_check(arg, msg, is_argument=True):
|
|||
return arg
|
||||
if isinstance(arg, _SpecialForm) or arg in (Generic, Protocol):
|
||||
raise TypeError(f"Plain {arg} is not valid as type argument")
|
||||
if isinstance(arg, (type, TypeVar, ForwardRef, types.Union)):
|
||||
if isinstance(arg, (type, TypeVar, ForwardRef, types.Union, ParamSpec)):
|
||||
return arg
|
||||
if not callable(arg):
|
||||
raise TypeError(f"{msg} Got {arg!r:.100}.")
|
||||
|
@ -183,14 +187,14 @@ def _type_repr(obj):
|
|||
|
||||
|
||||
def _collect_type_vars(types):
|
||||
"""Collect all type variable contained in types in order of
|
||||
first appearance (lexicographic order). For example::
|
||||
"""Collect all type variable-like variables contained
|
||||
in types in order of first appearance (lexicographic order). For example::
|
||||
|
||||
_collect_type_vars((T, List[S, T])) == (T, S)
|
||||
"""
|
||||
tvars = []
|
||||
for t in types:
|
||||
if isinstance(t, TypeVar) and t not in tvars:
|
||||
if isinstance(t, _TypeVarLike) 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])
|
||||
|
@ -208,6 +212,21 @@ def _check_generic(cls, parameters, elen):
|
|||
raise TypeError(f"Too {'many' if alen > elen else 'few'} parameters for {cls};"
|
||||
f" actual {alen}, expected {elen}")
|
||||
|
||||
def _prepare_paramspec_params(cls, params):
|
||||
"""Prepares the parameters for a Generic containing ParamSpec
|
||||
variables (internal helper).
|
||||
"""
|
||||
# Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612.
|
||||
if len(cls.__parameters__) == 1 and len(params) > 1:
|
||||
return (params,)
|
||||
else:
|
||||
_params = []
|
||||
# Convert lists to tuples to help other libraries cache the results.
|
||||
for p, tvar in zip(params, cls.__parameters__):
|
||||
if isinstance(tvar, ParamSpec) and isinstance(p, list):
|
||||
p = tuple(p)
|
||||
_params.append(p)
|
||||
return tuple(_params)
|
||||
|
||||
def _deduplicate(params):
|
||||
# Weed out strict duplicates, preserving the first of each occurrence.
|
||||
|
@ -523,6 +542,29 @@ def TypeAlias(self, parameters):
|
|||
raise TypeError(f"{self} is not subscriptable")
|
||||
|
||||
|
||||
@_SpecialForm
|
||||
def Concatenate(self, parameters):
|
||||
"""Used in conjunction with ParamSpec and Callable to represent a higher
|
||||
order function which adds, removes or transforms parameters of a Callable.
|
||||
|
||||
For example::
|
||||
|
||||
Callable[Concatenate[int, P], int]
|
||||
|
||||
See PEP 612 for detailed information.
|
||||
"""
|
||||
if parameters == ():
|
||||
raise TypeError("Cannot take a Concatenate of no types.")
|
||||
if not isinstance(parameters, tuple):
|
||||
parameters = (parameters,)
|
||||
if not isinstance(parameters[-1], ParamSpec):
|
||||
raise TypeError("The last parameter to Concatenate should be a "
|
||||
"ParamSpec variable.")
|
||||
msg = "Concatenate[arg, ...]: each arg must be a type."
|
||||
parameters = tuple(_type_check(p, msg) for p in parameters)
|
||||
return _ConcatenateGenericAlias(self, parameters)
|
||||
|
||||
|
||||
class ForwardRef(_Final, _root=True):
|
||||
"""Internal wrapper to hold a forward reference."""
|
||||
|
||||
|
@ -585,8 +627,41 @@ class ForwardRef(_Final, _root=True):
|
|||
def __repr__(self):
|
||||
return f'ForwardRef({self.__forward_arg__!r})'
|
||||
|
||||
class _TypeVarLike:
|
||||
"""Mixin for TypeVar-like types (TypeVar and ParamSpec)."""
|
||||
def __init__(self, bound, covariant, contravariant):
|
||||
"""Used to setup TypeVars and ParamSpec's bound, covariant and
|
||||
contravariant attributes.
|
||||
"""
|
||||
if covariant and contravariant:
|
||||
raise ValueError("Bivariant types are not supported.")
|
||||
self.__covariant__ = bool(covariant)
|
||||
self.__contravariant__ = bool(contravariant)
|
||||
if bound:
|
||||
self.__bound__ = _type_check(bound, "Bound must be a type.")
|
||||
else:
|
||||
self.__bound__ = None
|
||||
|
||||
class TypeVar(_Final, _Immutable, _root=True):
|
||||
def __or__(self, right):
|
||||
return Union[self, right]
|
||||
|
||||
def __ror__(self, right):
|
||||
return Union[self, right]
|
||||
|
||||
def __repr__(self):
|
||||
if self.__covariant__:
|
||||
prefix = '+'
|
||||
elif self.__contravariant__:
|
||||
prefix = '-'
|
||||
else:
|
||||
prefix = '~'
|
||||
return prefix + self.__name__
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__name__
|
||||
|
||||
|
||||
class TypeVar( _Final, _Immutable, _TypeVarLike, _root=True):
|
||||
"""Type variable.
|
||||
|
||||
Usage::
|
||||
|
@ -636,20 +711,13 @@ class TypeVar(_Final, _Immutable, _root=True):
|
|||
def __init__(self, name, *constraints, bound=None,
|
||||
covariant=False, contravariant=False):
|
||||
self.__name__ = name
|
||||
if covariant and contravariant:
|
||||
raise ValueError("Bivariant types are not supported.")
|
||||
self.__covariant__ = bool(covariant)
|
||||
self.__contravariant__ = bool(contravariant)
|
||||
super().__init__(bound, covariant, contravariant)
|
||||
if constraints and bound is not None:
|
||||
raise TypeError("Constraints cannot be combined with bound=...")
|
||||
if constraints and len(constraints) == 1:
|
||||
raise TypeError("A single constraint is not allowed")
|
||||
msg = "TypeVar(name, constraint, ...): constraints must be types."
|
||||
self.__constraints__ = tuple(_type_check(t, msg) for t in constraints)
|
||||
if bound:
|
||||
self.__bound__ = _type_check(bound, "Bound must be a type.")
|
||||
else:
|
||||
self.__bound__ = None
|
||||
try:
|
||||
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__') # for pickling
|
||||
except (AttributeError, ValueError):
|
||||
|
@ -657,23 +725,68 @@ class TypeVar(_Final, _Immutable, _root=True):
|
|||
if def_mod != 'typing':
|
||||
self.__module__ = def_mod
|
||||
|
||||
def __or__(self, right):
|
||||
return Union[self, right]
|
||||
|
||||
def __ror__(self, right):
|
||||
return Union[self, right]
|
||||
class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True):
|
||||
"""Parameter specification variable.
|
||||
|
||||
def __repr__(self):
|
||||
if self.__covariant__:
|
||||
prefix = '+'
|
||||
elif self.__contravariant__:
|
||||
prefix = '-'
|
||||
else:
|
||||
prefix = '~'
|
||||
return prefix + self.__name__
|
||||
Usage::
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__name__
|
||||
P = ParamSpec('P')
|
||||
|
||||
Parameter specification variables exist primarily for the benefit of static
|
||||
type checkers. They are used to forward the parameter types of one
|
||||
Callable to another Callable, a pattern commonly found in higher order
|
||||
functions and decorators. They are only valid when used in Concatenate, or
|
||||
as the first argument to Callable, or as parameters for user-defined Generics.
|
||||
See class Generic for more information on generic types. An example for
|
||||
annotating a decorator::
|
||||
|
||||
T = TypeVar('T')
|
||||
P = ParamSpec('P')
|
||||
|
||||
def add_logging(f: Callable[P, T]) -> Callable[P, T]:
|
||||
'''A type-safe decorator to add logging to a function.'''
|
||||
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
logging.info(f'{f.__name__} was called')
|
||||
return f(*args, **kwargs)
|
||||
return inner
|
||||
|
||||
@add_logging
|
||||
def add_two(x: float, y: float) -> float:
|
||||
'''Add two numbers together.'''
|
||||
return x + y
|
||||
|
||||
Parameter specification variables defined with covariant=True or
|
||||
contravariant=True can be used to declare covariant or contravariant
|
||||
generic types. These keyword arguments are valid, but their actual semantics
|
||||
are yet to be decided. See PEP 612 for details.
|
||||
|
||||
Parameter specification variables can be introspected. e.g.:
|
||||
|
||||
P.__name__ == 'T'
|
||||
P.__bound__ == None
|
||||
P.__covariant__ == False
|
||||
P.__contravariant__ == False
|
||||
|
||||
Note that only parameter specification variables defined in global scope can
|
||||
be pickled.
|
||||
"""
|
||||
|
||||
__slots__ = ('__name__', '__bound__', '__covariant__', '__contravariant__',
|
||||
'__dict__')
|
||||
|
||||
args = object()
|
||||
kwargs = object()
|
||||
|
||||
def __init__(self, name, bound=None, covariant=False, contravariant=False):
|
||||
self.__name__ = name
|
||||
super().__init__(bound, covariant, contravariant)
|
||||
try:
|
||||
def_mod = sys._getframe(1).f_globals.get('__name__', '__main__')
|
||||
except (AttributeError, ValueError):
|
||||
def_mod = None
|
||||
if def_mod != 'typing':
|
||||
self.__module__ = def_mod
|
||||
|
||||
|
||||
def _is_dunder(attr):
|
||||
|
@ -783,21 +896,26 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
|
|||
raise TypeError(f"Cannot subscript already-subscripted {self}")
|
||||
if not isinstance(params, tuple):
|
||||
params = (params,)
|
||||
msg = "Parameters to generic types must be types."
|
||||
params = tuple(_type_check(p, msg) for p in 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)
|
||||
_check_generic(self, params, len(self.__parameters__))
|
||||
|
||||
subst = dict(zip(self.__parameters__, params))
|
||||
new_args = []
|
||||
for arg in self.__args__:
|
||||
if isinstance(arg, TypeVar):
|
||||
if isinstance(arg, _TypeVarLike):
|
||||
arg = subst[arg]
|
||||
elif isinstance(arg, (_GenericAlias, GenericAlias)):
|
||||
subparams = arg.__parameters__
|
||||
if subparams:
|
||||
subargs = tuple(subst[x] for x in subparams)
|
||||
arg = arg[subargs]
|
||||
new_args.append(arg)
|
||||
# Required to flatten out the args for CallableGenericAlias
|
||||
if self.__origin__ == collections.abc.Callable and isinstance(arg, tuple):
|
||||
new_args.extend(arg)
|
||||
else:
|
||||
new_args.append(arg)
|
||||
return self.copy_with(tuple(new_args))
|
||||
|
||||
def copy_with(self, params):
|
||||
|
@ -884,15 +1002,18 @@ class _SpecialGenericAlias(_BaseGenericAlias, _root=True):
|
|||
class _CallableGenericAlias(_GenericAlias, _root=True):
|
||||
def __repr__(self):
|
||||
assert self._name == 'Callable'
|
||||
if len(self.__args__) == 2 and self.__args__[0] is Ellipsis:
|
||||
args = self.__args__
|
||||
if len(args) == 2 and (args[0] is Ellipsis
|
||||
or isinstance(args[0], (ParamSpec, _ConcatenateGenericAlias))):
|
||||
return super().__repr__()
|
||||
return (f'typing.Callable'
|
||||
f'[[{", ".join([_type_repr(a) for a in self.__args__[:-1]])}], '
|
||||
f'{_type_repr(self.__args__[-1])}]')
|
||||
f'[[{", ".join([_type_repr(a) for a in args[:-1]])}], '
|
||||
f'{_type_repr(args[-1])}]')
|
||||
|
||||
def __reduce__(self):
|
||||
args = self.__args__
|
||||
if not (len(args) == 2 and args[0] is ...):
|
||||
if not (len(args) == 2 and (args[0] is Ellipsis
|
||||
or isinstance(args[0], (ParamSpec, _ConcatenateGenericAlias)))):
|
||||
args = list(args[:-1]), args[-1]
|
||||
return operator.getitem, (Callable, args)
|
||||
|
||||
|
@ -992,6 +1113,10 @@ class _LiteralGenericAlias(_GenericAlias, _root=True):
|
|||
return hash(frozenset(_value_and_type_iter(self.__args__)))
|
||||
|
||||
|
||||
class _ConcatenateGenericAlias(_GenericAlias, _root=True):
|
||||
pass
|
||||
|
||||
|
||||
class Generic:
|
||||
"""Abstract base class for generic types.
|
||||
|
||||
|
@ -1022,18 +1147,20 @@ class Generic:
|
|||
if not params and cls is not Tuple:
|
||||
raise TypeError(
|
||||
f"Parameter list to {cls.__qualname__}[...] cannot be empty")
|
||||
msg = "Parameters to generic types must be types."
|
||||
params = tuple(_type_check(p, msg) for p in params)
|
||||
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, TypeVar) for p in params):
|
||||
if not all(isinstance(p, _TypeVarLike) for p in params):
|
||||
raise TypeError(
|
||||
f"Parameters to {cls.__name__}[...] must all be type variables")
|
||||
f"Parameters to {cls.__name__}[...] must all be type variables "
|
||||
f"or parameter specification variables.")
|
||||
if len(set(params)) != len(params):
|
||||
raise TypeError(
|
||||
f"Parameters to {cls.__name__}[...] must all be unique")
|
||||
else:
|
||||
# Subscripting a regular Generic subclass.
|
||||
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)
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Implemented :pep:`612`: added ``ParamSpec`` and ``Concatenate`` to
|
||||
:mod:`typing`. Patch by Ken Jin.
|
|
@ -156,13 +156,24 @@ error:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// isinstance(obj, TypeVar) without importing typing.py.
|
||||
// Returns -1 for errors.
|
||||
static int
|
||||
is_typevar(PyObject *obj)
|
||||
/* Checks if a variable number of names are from typing.py.
|
||||
* If any one of the names are found, return 1, else 0.
|
||||
**/
|
||||
static inline int
|
||||
is_typing_name(PyObject *obj, int num, ...)
|
||||
{
|
||||
va_list names;
|
||||
va_start(names, num);
|
||||
|
||||
PyTypeObject *type = Py_TYPE(obj);
|
||||
if (strcmp(type->tp_name, "TypeVar") != 0) {
|
||||
int hit = 0;
|
||||
for (int i = 0; i < num; ++i) {
|
||||
if (!strcmp(type->tp_name, va_arg(names, const char *))) {
|
||||
hit = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hit) {
|
||||
return 0;
|
||||
}
|
||||
PyObject *module = PyObject_GetAttrString((PyObject *)type, "__module__");
|
||||
|
@ -172,9 +183,25 @@ is_typevar(PyObject *obj)
|
|||
int res = PyUnicode_Check(module)
|
||||
&& _PyUnicode_EqualToASCIIString(module, "typing");
|
||||
Py_DECREF(module);
|
||||
|
||||
va_end(names);
|
||||
return res;
|
||||
}
|
||||
|
||||
// isinstance(obj, (TypeVar, ParamSpec)) without importing typing.py.
|
||||
// Returns -1 for errors.
|
||||
static inline int
|
||||
is_typevarlike(PyObject *obj)
|
||||
{
|
||||
return is_typing_name(obj, 2, "TypeVar", "ParamSpec");
|
||||
}
|
||||
|
||||
static inline int
|
||||
is_paramspec(PyObject *obj)
|
||||
{
|
||||
return is_typing_name(obj, 1, "ParamSpec");
|
||||
}
|
||||
|
||||
// Index of item in self[:len], or -1 if not found (self is a tuple)
|
||||
static Py_ssize_t
|
||||
tuple_index(PyObject *self, Py_ssize_t len, PyObject *item)
|
||||
|
@ -209,7 +236,7 @@ make_parameters(PyObject *args)
|
|||
Py_ssize_t iparam = 0;
|
||||
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
|
||||
PyObject *t = PyTuple_GET_ITEM(args, iarg);
|
||||
int typevar = is_typevar(t);
|
||||
int typevar = is_typevarlike(t);
|
||||
if (typevar < 0) {
|
||||
Py_DECREF(parameters);
|
||||
return NULL;
|
||||
|
@ -279,7 +306,14 @@ subs_tvars(PyObject *obj, PyObject *params, PyObject **argitems)
|
|||
if (iparam >= 0) {
|
||||
arg = argitems[iparam];
|
||||
}
|
||||
Py_INCREF(arg);
|
||||
// convert all the lists inside args to tuples to help
|
||||
// with caching in other libaries
|
||||
if (PyList_CheckExact(arg)) {
|
||||
arg = PyList_AsTuple(arg);
|
||||
}
|
||||
else {
|
||||
Py_INCREF(arg);
|
||||
}
|
||||
PyTuple_SET_ITEM(subargs, i, arg);
|
||||
}
|
||||
|
||||
|
@ -314,11 +348,19 @@ ga_getitem(PyObject *self, PyObject *item)
|
|||
int is_tuple = PyTuple_Check(item);
|
||||
Py_ssize_t nitems = is_tuple ? PyTuple_GET_SIZE(item) : 1;
|
||||
PyObject **argitems = is_tuple ? &PyTuple_GET_ITEM(item, 0) : &item;
|
||||
if (nitems != nparams) {
|
||||
return PyErr_Format(PyExc_TypeError,
|
||||
"Too %s arguments for %R",
|
||||
nitems > nparams ? "many" : "few",
|
||||
self);
|
||||
// A special case in PEP 612 where if X = Callable[P, int],
|
||||
// then X[int, str] == X[[int, str]].
|
||||
if (nparams == 1 && nitems > 1 && is_tuple &&
|
||||
is_paramspec(PyTuple_GET_ITEM(alias->parameters, 0))) {
|
||||
argitems = &item;
|
||||
}
|
||||
else {
|
||||
if (nitems != nparams) {
|
||||
return PyErr_Format(PyExc_TypeError,
|
||||
"Too %s arguments for %R",
|
||||
nitems > nparams ? "many" : "few",
|
||||
self);
|
||||
}
|
||||
}
|
||||
/* Replace all type variables (specified by alias->parameters)
|
||||
with corresponding values specified by argitems.
|
||||
|
@ -333,7 +375,7 @@ ga_getitem(PyObject *self, PyObject *item)
|
|||
}
|
||||
for (Py_ssize_t iarg = 0; iarg < nargs; iarg++) {
|
||||
PyObject *arg = PyTuple_GET_ITEM(alias->args, iarg);
|
||||
int typevar = is_typevar(arg);
|
||||
int typevar = is_typevarlike(arg);
|
||||
if (typevar < 0) {
|
||||
Py_DECREF(newargs);
|
||||
return NULL;
|
||||
|
@ -342,7 +384,13 @@ ga_getitem(PyObject *self, PyObject *item)
|
|||
Py_ssize_t iparam = tuple_index(alias->parameters, nparams, arg);
|
||||
assert(iparam >= 0);
|
||||
arg = argitems[iparam];
|
||||
Py_INCREF(arg);
|
||||
// convert lists to tuples to help with caching in other libaries.
|
||||
if (PyList_CheckExact(arg)) {
|
||||
arg = PyList_AsTuple(arg);
|
||||
}
|
||||
else {
|
||||
Py_INCREF(arg);
|
||||
}
|
||||
}
|
||||
else {
|
||||
arg = subs_tvars(arg, alias->parameters, argitems);
|
||||
|
|
Loading…
Reference in New Issue