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:
Matthew Rahtz 2022-11-27 03:56:00 +00:00 committed by GitHub
parent bc79b092c7
commit 2ae3b71e21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 186 additions and 0 deletions

View File

@ -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
=========