bpo-43224: Implement PEP 646 changes to typing.py (GH-31021)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
This commit is contained in:
Matthew Rahtz 2022-03-08 04:02:55 +00:00 committed by GitHub
parent 13331a12c3
commit 7a793a388b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 646 additions and 22 deletions

View File

@ -10,7 +10,7 @@ from unittest import TestCase, main, skipUnless, skip
from copy import copy, deepcopy
from typing import Any, NoReturn, Never, assert_never
from typing import TypeVar, AnyStr
from typing import TypeVar, TypeVarTuple, Unpack, AnyStr
from typing import T, KT, VT # Not in __all__.
from typing import Union, Optional, Literal
from typing import Tuple, List, Dict, MutableMapping
@ -370,6 +370,431 @@ class TypeVarTests(BaseTestCase):
list[T][arg]
class UnpackTests(BaseTestCase):
def test_accepts_single_type(self):
Unpack[Tuple[int]]
def test_rejects_multiple_types(self):
with self.assertRaises(TypeError):
Unpack[Tuple[int], Tuple[str]]
def test_rejects_multiple_parameterization(self):
with self.assertRaises(TypeError):
Unpack[Tuple[int]][Tuple[int]]
def test_cannot_be_called(self):
with self.assertRaises(TypeError):
Unpack()
class TypeVarTupleTests(BaseTestCase):
def test_instance_is_equal_to_itself(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(Ts, Ts)
def test_different_instances_are_different(self):
self.assertNotEqual(TypeVarTuple('Ts'), TypeVarTuple('Ts'))
def test_instance_isinstance_of_typevartuple(self):
Ts = TypeVarTuple('Ts')
self.assertIsInstance(Ts, TypeVarTuple)
def test_cannot_call_instance(self):
Ts = TypeVarTuple('Ts')
with self.assertRaises(TypeError):
Ts()
def test_unpacked_typevartuple_is_equal_to_itself(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(Unpack[Ts], Unpack[Ts])
def test_parameterised_tuple_is_equal_to_itself(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(tuple[Unpack[Ts]], tuple[Unpack[Ts]])
self.assertEqual(Tuple[Unpack[Ts]], Tuple[Unpack[Ts]])
def tests_tuple_arg_ordering_matters(self):
Ts1 = TypeVarTuple('Ts1')
Ts2 = TypeVarTuple('Ts2')
self.assertNotEqual(
tuple[Unpack[Ts1], Unpack[Ts2]],
tuple[Unpack[Ts2], Unpack[Ts1]],
)
self.assertNotEqual(
Tuple[Unpack[Ts1], Unpack[Ts2]],
Tuple[Unpack[Ts2], Unpack[Ts1]],
)
def test_tuple_args_and_parameters_are_correct(self):
Ts = TypeVarTuple('Ts')
t1 = tuple[Unpack[Ts]]
self.assertEqual(t1.__args__, (Unpack[Ts],))
self.assertEqual(t1.__parameters__, (Ts,))
t2 = Tuple[Unpack[Ts]]
self.assertEqual(t2.__args__, (Unpack[Ts],))
self.assertEqual(t2.__parameters__, (Ts,))
def test_repr_is_correct(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(repr(Ts), 'Ts')
self.assertEqual(repr(Unpack[Ts]), '*Ts')
self.assertEqual(repr(tuple[Unpack[Ts]]), 'tuple[*Ts]')
self.assertEqual(repr(Tuple[Unpack[Ts]]), 'typing.Tuple[*Ts]')
self.assertEqual(repr(Unpack[tuple[Unpack[Ts]]]), '*tuple[*Ts]')
self.assertEqual(repr(Unpack[Tuple[Unpack[Ts]]]), '*typing.Tuple[*Ts]')
def test_variadic_class_repr_is_correct(self):
Ts = TypeVarTuple('Ts')
class A(Generic[Unpack[Ts]]): pass
self.assertTrue(repr(A[()]).endswith('A[()]'))
self.assertTrue(repr(A[float]).endswith('A[float]'))
self.assertTrue(repr(A[float, str]).endswith('A[float, str]'))
self.assertTrue(repr(
A[Unpack[tuple[int, ...]]]
).endswith(
'A[*tuple[int, ...]]'
))
self.assertTrue(repr(
A[float, Unpack[tuple[int, ...]]]
).endswith(
'A[float, *tuple[int, ...]]'
))
self.assertTrue(repr(
A[Unpack[tuple[int, ...]], str]
).endswith(
'A[*tuple[int, ...], str]'
))
self.assertTrue(repr(
A[float, Unpack[tuple[int, ...]], str]
).endswith(
'A[float, *tuple[int, ...], str]'
))
def test_variadic_class_alias_repr_is_correct(self):
Ts = TypeVarTuple('Ts')
class A(Generic[Unpack[Ts]]): pass
B = A[Unpack[Ts]]
self.assertTrue(repr(B).endswith('A[*Ts]'))
with self.assertRaises(NotImplementedError):
B[()]
with self.assertRaises(NotImplementedError):
B[float]
with self.assertRaises(NotImplementedError):
B[float, str]
C = A[Unpack[Ts], int]
self.assertTrue(repr(C).endswith('A[*Ts, int]'))
with self.assertRaises(NotImplementedError):
C[()]
with self.assertRaises(NotImplementedError):
C[float]
with self.assertRaises(NotImplementedError):
C[float, str]
D = A[int, Unpack[Ts]]
self.assertTrue(repr(D).endswith('A[int, *Ts]'))
with self.assertRaises(NotImplementedError):
D[()]
with self.assertRaises(NotImplementedError):
D[float]
with self.assertRaises(NotImplementedError):
D[float, str]
E = A[int, Unpack[Ts], str]
self.assertTrue(repr(E).endswith('A[int, *Ts, str]'))
with self.assertRaises(NotImplementedError):
E[()]
with self.assertRaises(NotImplementedError):
E[float]
with self.assertRaises(NotImplementedError):
E[float, bool]
F = A[Unpack[Ts], Unpack[tuple[str, ...]]]
self.assertTrue(repr(F).endswith('A[*Ts, *tuple[str, ...]]'))
with self.assertRaises(NotImplementedError):
F[()]
with self.assertRaises(NotImplementedError):
F[float]
with self.assertRaises(NotImplementedError):
F[float, int]
def test_cannot_subclass_class(self):
with self.assertRaises(TypeError):
class C(TypeVarTuple): pass
def test_cannot_subclass_instance(self):
Ts = TypeVarTuple('Ts')
with self.assertRaises(TypeError):
class C(Ts): pass
with self.assertRaises(TypeError):
class C(Unpack[Ts]): pass
def test_variadic_class_args_are_correct(self):
T = TypeVar('T')
Ts = TypeVarTuple('Ts')
class A(Generic[Unpack[Ts]]): pass
B = A[()]
self.assertEqual(B.__args__, ())
C = A[int]
self.assertEqual(C.__args__, (int,))
D = A[int, str]
self.assertEqual(D.__args__, (int, str))
E = A[T]
self.assertEqual(E.__args__, (T,))
F = A[Unpack[Ts]]
self.assertEqual(F.__args__, (Unpack[Ts],))
G = A[T, Unpack[Ts]]
self.assertEqual(G.__args__, (T, Unpack[Ts]))
H = A[Unpack[Ts], T]
self.assertEqual(H.__args__, (Unpack[Ts], T))
def test_variadic_class_origin_is_correct(self):
Ts = TypeVarTuple('Ts')
class D(Generic[Unpack[Ts]]): pass
self.assertIs(D[int].__origin__, D)
self.assertIs(D[T].__origin__, D)
self.assertIs(D[Unpack[Ts]].__origin__, D)
def test_tuple_args_are_correct(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(tuple[Unpack[Ts]].__args__, (Unpack[Ts],))
self.assertEqual(Tuple[Unpack[Ts]].__args__, (Unpack[Ts],))
self.assertEqual(tuple[Unpack[Ts], int].__args__, (Unpack[Ts], int))
self.assertEqual(Tuple[Unpack[Ts], int].__args__, (Unpack[Ts], int))
self.assertEqual(tuple[int, Unpack[Ts]].__args__, (int, Unpack[Ts]))
self.assertEqual(Tuple[int, Unpack[Ts]].__args__, (int, Unpack[Ts]))
self.assertEqual(tuple[int, Unpack[Ts], str].__args__,
(int, Unpack[Ts], str))
self.assertEqual(Tuple[int, Unpack[Ts], str].__args__,
(int, Unpack[Ts], str))
self.assertEqual(tuple[Unpack[Ts], int].__args__, (Unpack[Ts], int))
self.assertEqual(Tuple[Unpack[Ts]].__args__, (Unpack[Ts],))
def test_callable_args_are_correct(self):
Ts = TypeVarTuple('Ts')
Ts1 = TypeVarTuple('Ts1')
Ts2 = TypeVarTuple('Ts2')
# TypeVarTuple in the arguments
a = Callable[[Unpack[Ts]], None]
self.assertEqual(a.__args__, (Unpack[Ts], type(None)))
b = Callable[[int, Unpack[Ts]], None]
self.assertEqual(b.__args__, (int, Unpack[Ts], type(None)))
c = Callable[[Unpack[Ts], int], None]
self.assertEqual(c.__args__, (Unpack[Ts], int, type(None)))
d = Callable[[str, Unpack[Ts], int], None]
self.assertEqual(d.__args__, (str, Unpack[Ts], int, type(None)))
# TypeVarTuple as the return
e = Callable[[None], Unpack[Ts]]
self.assertEqual(e.__args__, (type(None), Unpack[Ts]))
f = Callable[[None], tuple[int, Unpack[Ts]]]
self.assertEqual(f.__args__, (type(None), tuple[int, Unpack[Ts]]))
g = Callable[[None], tuple[Unpack[Ts], int]]
self.assertEqual(g.__args__, (type(None), tuple[Unpack[Ts], int]))
h = Callable[[None], tuple[str, Unpack[Ts], int]]
self.assertEqual(h.__args__, (type(None), tuple[str, Unpack[Ts], int]))
# TypeVarTuple in both
i = Callable[[Unpack[Ts]], Unpack[Ts]]
self.assertEqual(i.__args__, (Unpack[Ts], Unpack[Ts]))
j = Callable[[Unpack[Ts1]], Unpack[Ts2]]
self.assertEqual(j.__args__, (Unpack[Ts1], Unpack[Ts2]))
def test_variadic_class_with_duplicate_typevartuples_fails(self):
Ts1 = TypeVarTuple('Ts1')
Ts2 = TypeVarTuple('Ts2')
with self.assertRaises(TypeError):
class C(Generic[Unpack[Ts1], Unpack[Ts1]]): pass
with self.assertRaises(TypeError):
class C(Generic[Unpack[Ts1], Unpack[Ts2], Unpack[Ts1]]): pass
def test_type_concatenation_in_variadic_class_argument_list_succeeds(self):
Ts = TypeVarTuple('Ts')
class C(Generic[Unpack[Ts]]): pass
C[int, Unpack[Ts]]
C[Unpack[Ts], int]
C[int, Unpack[Ts], str]
C[int, bool, Unpack[Ts], float, str]
def test_type_concatenation_in_tuple_argument_list_succeeds(self):
Ts = TypeVarTuple('Ts')
tuple[int, Unpack[Ts]]
tuple[Unpack[Ts], int]
tuple[int, Unpack[Ts], str]
tuple[int, bool, Unpack[Ts], float, str]
Tuple[int, Unpack[Ts]]
Tuple[Unpack[Ts], int]
Tuple[int, Unpack[Ts], str]
Tuple[int, bool, Unpack[Ts], float, str]
def test_variadic_class_definition_using_packed_typevartuple_fails(self):
Ts = TypeVarTuple('Ts')
with self.assertRaises(TypeError):
class C(Generic[Ts]): pass
def test_variadic_class_definition_using_concrete_types_fails(self):
Ts = TypeVarTuple('Ts')
with self.assertRaises(TypeError):
class E(Generic[Unpack[Ts], int]): pass
def test_variadic_class_with_2_typevars_accepts_2_or_more_args(self):
Ts = TypeVarTuple('Ts')
T1 = TypeVar('T1')
T2 = TypeVar('T2')
class A(Generic[T1, T2, Unpack[Ts]]): pass
A[int, str]
A[int, str, float]
A[int, str, float, bool]
class B(Generic[T1, Unpack[Ts], T2]): pass
B[int, str]
B[int, str, float]
B[int, str, float, bool]
class C(Generic[Unpack[Ts], T1, T2]): pass
C[int, str]
C[int, str, float]
C[int, str, float, bool]
def test_variadic_args_annotations_are_correct(self):
Ts = TypeVarTuple('Ts')
def f(*args: Unpack[Ts]): pass
self.assertEqual(f.__annotations__, {'args': Unpack[Ts]})
def test_variadic_args_with_ellipsis_annotations_are_correct(self):
Ts = TypeVarTuple('Ts')
def a(*args: Unpack[tuple[int, ...]]): pass
self.assertEqual(a.__annotations__,
{'args': Unpack[tuple[int, ...]]})
def b(*args: Unpack[Tuple[int, ...]]): pass
self.assertEqual(b.__annotations__,
{'args': Unpack[Tuple[int, ...]]})
def test_concatenation_in_variadic_args_annotations_are_correct(self):
Ts = TypeVarTuple('Ts')
# Unpacking using `Unpack`, native `tuple` type
def a(*args: Unpack[tuple[int, Unpack[Ts]]]): pass
self.assertEqual(
a.__annotations__,
{'args': Unpack[tuple[int, Unpack[Ts]]]},
)
def b(*args: Unpack[tuple[Unpack[Ts], int]]): pass
self.assertEqual(
b.__annotations__,
{'args': Unpack[tuple[Unpack[Ts], int]]},
)
def c(*args: Unpack[tuple[str, Unpack[Ts], int]]): pass
self.assertEqual(
c.__annotations__,
{'args': Unpack[tuple[str, Unpack[Ts], int]]},
)
def d(*args: Unpack[tuple[int, bool, Unpack[Ts], float, str]]): pass
self.assertEqual(
d.__annotations__,
{'args': Unpack[tuple[int, bool, Unpack[Ts], float, str]]},
)
# Unpacking using `Unpack`, `Tuple` type from typing.py
def e(*args: Unpack[Tuple[int, Unpack[Ts]]]): pass
self.assertEqual(
e.__annotations__,
{'args': Unpack[Tuple[int, Unpack[Ts]]]},
)
def f(*args: Unpack[Tuple[Unpack[Ts], int]]): pass
self.assertEqual(
f.__annotations__,
{'args': Unpack[Tuple[Unpack[Ts], int]]},
)
def g(*args: Unpack[Tuple[str, Unpack[Ts], int]]): pass
self.assertEqual(
g.__annotations__,
{'args': Unpack[Tuple[str, Unpack[Ts], int]]},
)
def h(*args: Unpack[Tuple[int, bool, Unpack[Ts], float, str]]): pass
self.assertEqual(
h.__annotations__,
{'args': Unpack[Tuple[int, bool, Unpack[Ts], float, str]]},
)
def test_variadic_class_same_args_results_in_equalty(self):
Ts = TypeVarTuple('Ts')
class C(Generic[Unpack[Ts]]): pass
self.assertEqual(C[int], C[int])
Ts1 = TypeVarTuple('Ts1')
Ts2 = TypeVarTuple('Ts2')
self.assertEqual(
C[Unpack[Ts1]],
C[Unpack[Ts1]],
)
self.assertEqual(
C[Unpack[Ts1], Unpack[Ts2]],
C[Unpack[Ts1], Unpack[Ts2]],
)
self.assertEqual(
C[int, Unpack[Ts1], Unpack[Ts2]],
C[int, Unpack[Ts1], Unpack[Ts2]],
)
def test_variadic_class_arg_ordering_matters(self):
Ts = TypeVarTuple('Ts')
class C(Generic[Unpack[Ts]]): pass
self.assertNotEqual(
C[int, str],
C[str, int],
)
Ts1 = TypeVarTuple('Ts1')
Ts2 = TypeVarTuple('Ts2')
self.assertNotEqual(
C[Unpack[Ts1], Unpack[Ts2]],
C[Unpack[Ts2], Unpack[Ts1]],
)
def test_variadic_class_arg_typevartuple_identity_matters(self):
Ts = TypeVarTuple('Ts')
class C(Generic[Unpack[Ts]]): pass
Ts1 = TypeVarTuple('Ts1')
Ts2 = TypeVarTuple('Ts2')
self.assertNotEqual(C[Unpack[Ts1]], C[Unpack[Ts2]])
class UnionTests(BaseTestCase):
def test_basics(self):
@ -1819,6 +2244,11 @@ class GenericTests(BaseTestCase):
class MyGeneric(Generic[T], Generic[S]): ...
with self.assertRaises(TypeError):
class MyGeneric(List[T], Generic[S]): ...
with self.assertRaises(TypeError):
Generic[()]
class C(Generic[T]): pass
with self.assertRaises(TypeError):
C[()]
def test_init(self):
T = TypeVar('T')

View File

@ -5,7 +5,7 @@ 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, Never, ClassVar, Union, Optional, Concatenate
Any, NoReturn, Never, ClassVar, Union, Optional, Concatenate, Unpack
* 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
@ -56,6 +56,7 @@ __all__ = [
'Tuple',
'Type',
'TypeVar',
'TypeVarTuple',
'Union',
# ABCs (from collections.abc).
@ -139,6 +140,7 @@ __all__ = [
'TYPE_CHECKING',
'TypeAlias',
'TypeGuard',
'Unpack',
]
# The pseudo-submodules 're' and 'io' are part of the public
@ -182,7 +184,7 @@ def _type_check(arg, msg, is_argument=True, module=None, *, allow_special_forms=
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.UnionType, ParamSpec,
ParamSpecArgs, ParamSpecKwargs)):
ParamSpecArgs, ParamSpecKwargs, TypeVarTuple)):
return arg
if not callable(arg):
raise TypeError(f"{msg} Got {arg!r:.100}.")
@ -793,8 +795,28 @@ class ForwardRef(_Final, _root=True):
module_repr = f', module={self.__forward_module__!r}'
return f'ForwardRef({self.__forward_arg__!r}{module_repr})'
class _TypeVarLike:
"""Mixin for TypeVar-like types (TypeVar and ParamSpec)."""
def _is_unpacked_typevartuple(x: Any) -> bool:
return (
isinstance(x, _UnpackGenericAlias)
# If x is Unpack[tuple[...]], __parameters__ will be empty.
and x.__parameters__
and isinstance(x.__parameters__[0], TypeVarTuple)
)
def _is_typevar_like(x: Any) -> bool:
return isinstance(x, (TypeVar, ParamSpec)) or _is_unpacked_typevartuple(x)
class _BoundVarianceMixin:
"""Mixin giving __init__ bound and variance arguments.
This is used by TypeVar and ParamSpec, which both employ the notions of
a type 'bound' (restricting type arguments to be a subtype of some
specified type) and type 'variance' (determining subtype relations between
generic types).
"""
def __init__(self, bound, covariant, contravariant):
"""Used to setup TypeVars and ParamSpec's bound, covariant and
contravariant attributes.
@ -827,7 +849,7 @@ class _TypeVarLike:
return self.__name__
class TypeVar( _Final, _Immutable, _TypeVarLike, _root=True):
class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _root=True):
"""Type variable.
Usage::
@ -886,6 +908,39 @@ class TypeVar( _Final, _Immutable, _TypeVarLike, _root=True):
self.__module__ = def_mod
class TypeVarTuple(_Final, _Immutable, _root=True):
"""Type variable tuple.
Usage:
Ts = TypeVarTuple('Ts') # Can be given any name
Just as a TypeVar (type variable) is a placeholder for a single type,
a TypeVarTuple is a placeholder for an *arbitrary* number of types. For
example, if we define a generic class using a TypeVarTuple:
class C(Generic[*Ts]): ...
Then we can parameterize that class with an arbitrary number of type
arguments:
C[int] # Fine
C[int, str] # Also fine
C[()] # Even this is fine
For more details, see PEP 646.
"""
def __init__(self, name):
self._name = name
def __iter__(self):
yield Unpack[self]
def __repr__(self):
return self._name
class ParamSpecArgs(_Final, _Immutable, _root=True):
"""The args for a ParamSpec object.
@ -934,7 +989,7 @@ class ParamSpecKwargs(_Final, _Immutable, _root=True):
return self.__origin__ == other.__origin__
class ParamSpec(_Final, _Immutable, _TypeVarLike, _root=True):
class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _root=True):
"""Parameter specification variable.
Usage::
@ -1065,6 +1120,45 @@ class _BaseGenericAlias(_Final, _root=True):
return list(set(super().__dir__()
+ [attr for attr in dir(self.__origin__) if not _is_dunder(attr)]))
def _is_unpacked_tuple(x: Any) -> bool:
# Is `x` something like `*tuple[int]` or `*tuple[int, ...]`?
if not isinstance(x, _UnpackGenericAlias):
return False
# Alright, `x` is `Unpack[something]`.
# `x` will always have `__args__`, because Unpack[] and Unpack[()]
# aren't legal.
unpacked_type = x.__args__[0]
return getattr(unpacked_type, '__origin__', None) is tuple
def _is_unpacked_arbitrary_length_tuple(x: Any) -> bool:
if not _is_unpacked_tuple(x):
return False
unpacked_tuple = x.__args__[0]
if not hasattr(unpacked_tuple, '__args__'):
# It's `Unpack[tuple]`. We can't make any assumptions about the length
# of the tuple, so it's effectively an arbitrary-length tuple.
return True
tuple_args = unpacked_tuple.__args__
if not tuple_args:
# It's `Unpack[tuple[()]]`.
return False
last_arg = tuple_args[-1]
if last_arg is Ellipsis:
# It's `Unpack[tuple[something, ...]]`, which is arbitrary-length.
return True
# If the arguments didn't end with an ellipsis, then it's not an
# arbitrary-length tuple.
return False
# Special typing constructs Union, Optional, Generic, Callable and Tuple
# use three special attributes for internal bookkeeping of generic types:
# * __parameters__ is a tuple of unique free type parameters of a generic
@ -1103,7 +1197,7 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
# TypeVar[bool]
def __init__(self, origin, args, *, inst=True, name=None,
_typevar_types=TypeVar,
_typevar_types=(TypeVar, TypeVarTuple),
_paramspec_tvars=False):
super().__init__(origin, inst=inst, name=name)
if not isinstance(args, tuple):
@ -1160,7 +1254,10 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
if (self._paramspec_tvars
and any(isinstance(t, ParamSpec) for t in self.__parameters__)):
args = _prepare_paramspec_params(self, args)
else:
elif not any(isinstance(p, TypeVarTuple) for p in self.__parameters__):
# We only run this if there are no TypeVarTuples, because we
# don't check variadic generic arity at runtime (to reduce
# complexity of typing.py).
_check_generic(self, args, len(self.__parameters__))
new_args = self._determine_new_args(args)
@ -1182,6 +1279,10 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
# anything more exotic than a plain `TypeVar`, we need to consider
# edge cases.
if any(isinstance(p, TypeVarTuple) for p in self.__parameters__):
raise NotImplementedError(
"Type substitution for TypeVarTuples is not yet implemented"
)
# In the example above, this would be {T3: str}
new_arg_by_param = dict(zip(self.__parameters__, args))
@ -1195,6 +1296,10 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
f"ParamSpec, or Concatenate. Got {new_arg}")
elif isinstance(old_arg, self._typevar_types):
new_arg = new_arg_by_param[old_arg]
elif (TypeVarTuple in self._typevar_types
and _is_unpacked_typevartuple(old_arg)):
original_typevartuple = old_arg.__parameters__[0]
new_arg = new_arg_by_param[original_typevartuple]
elif isinstance(old_arg, (_GenericAlias, GenericAlias, types.UnionType)):
subparams = old_arg.__parameters__
if not subparams:
@ -1217,6 +1322,17 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
# ...we need to be careful; `new_args` should end up as
# `(int, str, float)` rather than `([int, str], float)`.
new_args.extend(new_arg)
elif _is_unpacked_typevartuple(old_arg):
# Consider the following `_GenericAlias`, `B`:
# class A(Generic[*Ts]): ...
# B = A[T, *Ts]
# If we then do:
# B[float, int, str]
# The `new_arg` corresponding to `T` will be `float`, and the
# `new_arg` corresponding to `*Ts` will be `(int, str)`. We
# should join all these types together in a flat list
# `(float, int, str)` - so again, we should `extend`.
new_args.extend(new_arg)
else:
new_args.append(new_arg)
@ -1230,7 +1346,11 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
name = 'typing.' + self._name
else:
name = _type_repr(self.__origin__)
args = ", ".join([_type_repr(a) for a in self.__args__])
if self.__args__:
args = ", ".join([_type_repr(a) for a in self.__args__])
else:
# To ensure the repr is eval-able.
args = "()"
return f'{name}[{args}]'
def __reduce__(self):
@ -1258,6 +1378,9 @@ class _GenericAlias(_BaseGenericAlias, _root=True):
return ()
return (self.__origin__,)
def __iter__(self):
yield Unpack[self]
# _nparams is the number of accepted parameters, e.g. 0 for Hashable,
# 1 for List and 2 for Dict. It may be -1 if variable number of
@ -1365,10 +1488,10 @@ class _TupleType(_SpecialGenericAlias, _root=True):
return self.copy_with((_TypingEmpty,))
if not isinstance(params, tuple):
params = (params,)
if len(params) == 2 and params[1] is ...:
if len(params) >= 2 and params[-1] is ...:
msg = "Tuple[t, ...]: t must be a type."
p = _type_check(params[0], msg)
return self.copy_with((p, _TypingEllipsis))
params = tuple(_type_check(p, msg) for p in params[:-1])
return self.copy_with((*params, _TypingEllipsis))
msg = "Tuple[t0, t1, ...]: each t must be a type."
params = tuple(_type_check(p, msg) for p in params)
return self.copy_with(params)
@ -1441,6 +1564,48 @@ class _ConcatenateGenericAlias(_GenericAlias, _root=True):
return super().copy_with(params)
@_SpecialForm
def Unpack(self, parameters):
"""Type unpack operator.
The type unpack operator takes the child types from some container type,
such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For
example:
# For some generic class `Foo`:
Foo[Unpack[tuple[int, str]]] # Equivalent to Foo[int, str]
Ts = TypeVarTuple('Ts')
# Specifies that `Bar` is generic in an arbitrary number of types.
# (Think of `Ts` as a tuple of an arbitrary number of individual
# `TypeVar`s, which the `Unpack` is 'pulling out' directly into the
# `Generic[]`.)
class Bar(Generic[Unpack[Ts]]): ...
Bar[int] # Valid
Bar[int, str] # Also valid
From Python 3.11, this can also be done using the `*` operator:
Foo[*tuple[int, str]]
class Bar(Generic[*Ts]): ...
Note that there is only some runtime checking of this operator. Not
everything the runtime allows may be accepted by static type checkers.
For more information, see PEP 646.
"""
item = _type_check(parameters, f'{self} accepts only single type.')
return _UnpackGenericAlias(origin=self, args=(item,))
class _UnpackGenericAlias(_GenericAlias, _root=True):
def __repr__(self):
# `Unpack` only takes one argument, so __args__ should contain only
# a single item.
return '*' + repr(self.__args__[0])
class Generic:
"""Abstract base class for generic types.
@ -1466,15 +1631,36 @@ class Generic:
@_tp_cache
def __class_getitem__(cls, params):
"""Parameterizes a generic class.
At least, parameterizing a generic class is the *main* thing this method
does. For example, for some generic class `Foo`, this is called when we
do `Foo[int]` - there, with `cls=Foo` and `params=int`.
However, note that this method is also called when defining generic
classes in the first place with `class Foo(Generic[T]): ...`.
"""
if not isinstance(params, tuple):
params = (params,)
if not params and cls is not Tuple:
raise TypeError(
f"Parameter list to {cls.__qualname__}[...] cannot be empty")
if not params:
# We're only ok with `params` being empty if the class's only type
# parameter is a `TypeVarTuple` (which can contain zero types).
class_params = getattr(cls, "__parameters__", None)
only_class_parameter_is_typevartuple = (
class_params is not None
and len(class_params) == 1
and isinstance(class_params[0], TypeVarTuple)
)
if not only_class_parameter_is_typevartuple:
raise TypeError(
f"Parameter list to {cls.__qualname__}[...] cannot be empty"
)
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, ParamSpec)) for p in params):
if not all(_is_typevar_like(p) for p in params):
raise TypeError(
f"Parameters to {cls.__name__}[...] must all be type variables "
f"or parameter specification variables.")
@ -1485,11 +1671,16 @@ class Generic:
# Subscripting a regular Generic subclass.
if any(isinstance(t, ParamSpec) for t in cls.__parameters__):
params = _prepare_paramspec_params(cls, params)
else:
elif not any(isinstance(p, TypeVarTuple) for p in cls.__parameters__):
# We only run this if there are no TypeVarTuples, because we
# don't check variadic generic arity at runtime (to reduce
# complexity of typing.py).
_check_generic(cls, params, len(cls.__parameters__))
return _GenericAlias(cls, params,
_typevar_types=(TypeVar, ParamSpec),
_paramspec_tvars=True)
return _GenericAlias(
cls, params,
_typevar_types=(TypeVar, TypeVarTuple, ParamSpec),
_paramspec_tvars=True,
)
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
@ -1501,7 +1692,9 @@ class Generic:
if error:
raise TypeError("Cannot inherit from plain Generic")
if '__orig_bases__' in cls.__dict__:
tvars = _collect_type_vars(cls.__orig_bases__, (TypeVar, ParamSpec))
tvars = _collect_type_vars(
cls.__orig_bases__, (TypeVar, TypeVarTuple, ParamSpec)
)
# Look for Generic[T1, ..., Tn].
# If found, tvars must be a subset of it.
# If not found, tvars is it.

View File

@ -0,0 +1 @@
Implement support for PEP 646 in typing.py.