PEP 646: Add section on type substitution in generic aliases (#2883)
Co-authored-by: C.A.M. Gerlach <CAM.Gerlach@Gerlach.CAM>
This commit is contained in:
parent
bc79b092c7
commit
2ae3b71e21
186
pep-0646.rst
186
pep-0646.rst
|
@ -722,6 +722,187 @@ Normal ``TypeVar`` instances can also be used in such aliases:
|
|||
# T bound to Any, Ts to an Tuple[Any, ...]
|
||||
Foo
|
||||
|
||||
|
||||
Substitution in Aliases
|
||||
-----------------------
|
||||
|
||||
In the previous section, we only discussed simple usage of generic aliases
|
||||
in which the type arguments were just simple types. However, a number of
|
||||
more exotic constructions are also possible.
|
||||
|
||||
|
||||
Type Arguments can be Variadic
|
||||
''''''''''''''''''''''''''''''
|
||||
|
||||
First, type arguments to generic aliases can be variadic. For example, a
|
||||
``TypeVarTuple`` can be used as a type argument:
|
||||
|
||||
::
|
||||
|
||||
Ts1 = TypeVar('Ts1')
|
||||
Ts2 = TypeVar('Ts2')
|
||||
|
||||
IntTuple = Tuple[int, *Ts1]
|
||||
IntFloatTuple = IntTuple[float, *Ts2] # Valid
|
||||
|
||||
Here, ``*Ts1`` in the ``IntTuple`` alias is bound to ``Tuple[float, *Ts2]``,
|
||||
resulting in an alias ``IntFloatTuple`` equivalent to
|
||||
``Tuple[int, float, *Ts2]``.
|
||||
|
||||
Unpacked arbitrary-length tuples can also be used as type arguments, with
|
||||
similar effects:
|
||||
|
||||
::
|
||||
|
||||
IntFloatsTuple = IntTuple[*Tuple[float, ...]] # Valid
|
||||
|
||||
Here, ``*Ts1`` is bound to ``*Tuple[float, ...]``, resulting in
|
||||
``IntFloatsTuple`` being equivalent to ``Tuple[int, *Tuple[float, ...]]``: a tuple
|
||||
consisting of an ``int`` then zero or more ``float``\s.
|
||||
|
||||
|
||||
Variadic Arguments Require Variadic Aliases
|
||||
'''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
Variadic type arguments can only be used with generic aliases that are
|
||||
themselves variadic. For example:
|
||||
|
||||
::
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
IntTuple = Tuple[int, T]
|
||||
|
||||
IntTuple[str] # Valid
|
||||
IntTuple[*Ts] # NOT valid
|
||||
IntTuple[*Tuple[float, ...]] # NOT valid
|
||||
|
||||
Here, ``IntTuple`` is a *non*-variadic generic alias that takes exactly one
|
||||
type argument. Hence, it cannot accept ``*Ts`` or ``*Tuple[float, ...]`` as type
|
||||
arguments, because they represent an arbitrary number of types.
|
||||
|
||||
|
||||
Aliases with Both TypeVars and TypeVarTuples
|
||||
''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
In `Aliases`_, we briefly mentioned that aliases can be generic in both
|
||||
``TypeVar``\s and ``TypeVarTuple``\s:
|
||||
|
||||
::
|
||||
|
||||
T = TypeVar('T')
|
||||
Foo = Tuple[T, *Ts]
|
||||
|
||||
Foo[str, int] # T bound to str, Ts to Tuple[int]
|
||||
Foo[str, int, float] # T bound to str, Ts to Tuple[int, float]
|
||||
|
||||
In accordance with `Multiple Type Variable Tuples: Not Allowed`_, at most one
|
||||
``TypeVarTuple`` may appear in the type parameters to an alias. However, a
|
||||
``TypeVarTuple`` can be combined with an arbitrary number of ``TypeVar``\s,
|
||||
both before and after:
|
||||
|
||||
::
|
||||
|
||||
T1 = TypeVar('T1')
|
||||
T2 = TypeVar('T2')
|
||||
T3 = TypeVar('T3')
|
||||
|
||||
Tuple[*Ts, T1, T2] # Valid
|
||||
Tuple[T1, T2, *Ts] # Valid
|
||||
Tuple[T1, *Ts, T2, T3] # Valid
|
||||
|
||||
In order to substitute these type variables with supplied type arguments,
|
||||
any type variables at the beginning or end of the type parameter list first
|
||||
consume type arguments, and then any remaining type arguments are bound
|
||||
to the ``TypeVarTuple``:
|
||||
|
||||
::
|
||||
|
||||
Shrubbery = Tuple[*Ts, T1, T2]
|
||||
|
||||
Shrubbery[str, bool] # T2=bool, T1=str, Ts=Tuple[()]
|
||||
Shrubbery[str, bool, float] # T2=float, T1=bool, Ts=Tuple[str]
|
||||
Shrubbery[str, bool, float, int] # T2=int, T1=float, Ts=Tuple[str, bool]
|
||||
|
||||
Ptang = Tuple[T1, *Ts, T2, T3]
|
||||
|
||||
Ptang[str, bool, float] # T1=str, T3=float, T2=bool, Ts=Tuple[()]
|
||||
Ptang[str, bool, float, int] # T1=str, T3=int, T2=float, Ts=Tuple[bool]
|
||||
|
||||
Note that the minimum number of type arguments in such cases is set by
|
||||
the number of ``TypeVar``\s:
|
||||
|
||||
::
|
||||
|
||||
Shrubbery[int] # Not valid; Shrubbery needs at least two type arguments
|
||||
|
||||
|
||||
Splitting Arbitrary-Length Tuples
|
||||
'''''''''''''''''''''''''''''''''
|
||||
|
||||
A final complication occurs when an unpacked arbitrary-length tuple is used
|
||||
as a type argument to an alias consisting of both ``TypeVar``\s and a
|
||||
``TypeVarTuple``:
|
||||
|
||||
::
|
||||
|
||||
Elderberries = Tuple[*Ts, T1]
|
||||
Hamster = Elderberries[*Tuple[int, ...]] # valid
|
||||
|
||||
In such cases, the arbitrary-length tuple is split between the ``TypeVar``\s
|
||||
and the ``TypeVarTuple``. We assume the arbitrary-length tuple contains
|
||||
at least as many items as there are ``TypeVar``\s, such that individual
|
||||
instances of the inner type - here ``int`` - are bound to any ``TypeVar``\s
|
||||
present. The 'rest' of the arbitrary-length tuple - here ``*Tuple[int, ...]``,
|
||||
since a tuple of arbitrary length minus two items is still arbitrary-length -
|
||||
is bound to the ``TypeVarTuple``.
|
||||
|
||||
Here, therefore, ``Hamster`` is equivalent to ``Tuple[*Tuple[int, ...], int]``:
|
||||
a tuple consisting of zero or more ``int``\s, then a final ``int``.
|
||||
|
||||
Of course, such splitting only occurs if necessary. For example, if we instead
|
||||
did:
|
||||
|
||||
::
|
||||
|
||||
Elderberries[*Tuple[int, ...], str]
|
||||
|
||||
Then splitting would not occur; ``T1`` would be bound to ``str``, and
|
||||
``Ts`` to ``*Tuple[int, ...]``.
|
||||
|
||||
In particularly awkward cases, a ``TypeVarTuple`` may consume both a type
|
||||
*and* a part of an arbitrary-length tuple type:
|
||||
|
||||
::
|
||||
|
||||
Elderberries[str, *Tuple[int, ...]]
|
||||
|
||||
Here, ``T1`` is bound to ``int``, and ``Ts`` is bound to
|
||||
``Tuple[str, *Tuple[int, ...]]``. This expression is therefore equivalent to
|
||||
``Tuple[str, *Tuple[int, ...], int]``: a tuple consisting of a ``str``, then
|
||||
zero or more ``int``\s, ending with an ``int``.
|
||||
|
||||
|
||||
TypeVarTuples Cannot be Split
|
||||
'''''''''''''''''''''''''''''
|
||||
|
||||
Finally, although any arbitrary-length tuples in the type argument list can be
|
||||
split between the type variables and the type variable tuple, the same is not
|
||||
true of ``TypeVarTuple``\s in the argument list:
|
||||
|
||||
::
|
||||
|
||||
Ts1 = TypeVarTuple('Ts1')
|
||||
Ts2 = TypeVarTuple('Ts2')
|
||||
|
||||
Camelot = Tuple[T, *Ts1]
|
||||
Camelot[*Ts2] # NOT valid
|
||||
|
||||
This is not possible because, unlike in the case of an unpacked arbitrary-length
|
||||
tuple, there is no way to 'peer inside' the ``TypeVarTuple`` to see what its
|
||||
individual types are.
|
||||
|
||||
|
||||
Overloads for Accessing Individual Types
|
||||
----------------------------------------
|
||||
|
||||
|
@ -1459,6 +1640,9 @@ Expanding on these ideas, **Mark Mendoza** and **Vincent Siles** gave a presenta
|
|||
'Variadic Type Variables for Decorators and Tensors' [#variadic-type-variables]_ at the 2019 Python
|
||||
Typing Summit.
|
||||
|
||||
Discussion over how type substitution in generic aliases should behave
|
||||
took place in `cpython#91162`_.
|
||||
|
||||
|
||||
References
|
||||
==========
|
||||
|
@ -1502,6 +1686,8 @@ References
|
|||
|
||||
.. [#dan-endorsement] https://mail.python.org/archives/list/python-dev@python.org/message/HTCARTYYCHETAMHB6OVRNR5EW5T2CP4J/
|
||||
|
||||
.. _cpython#91162: https://github.com/python/cpython/issues/91162
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
|
|
Loading…
Reference in New Issue