Skip to content

Commit

Permalink
bpo-47097: Add documentation for TypeVarTuple (#32103)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Waygood <[email protected]>
Co-authored-by: Jelle Zijlstra <[email protected]>
  • Loading branch information
3 people authored Apr 4, 2022
1 parent c9844cb commit 38ae5b8
Showing 1 changed file with 119 additions and 0 deletions.
119 changes: 119 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ annotations. These include:
*Introducing* :class:`ParamSpec` and :data:`Concatenate`
* :pep:`613`: Explicit Type Aliases
*Introducing* :data:`TypeAlias`
* :pep:`646`: Variadic Generics
*Introducing* :data:`TypeVarTuple`
* :pep:`647`: User-Defined Type Guards
*Introducing* :data:`TypeGuard`
* :pep:`673`: Self type
Expand Down Expand Up @@ -1230,6 +1232,123 @@ These are not used in annotations. They are building blocks for creating generic
``covariant=True`` or ``contravariant=True``. See :pep:`484` for more
details. By default, type variables are invariant.

.. class:: TypeVarTuple

Type variable tuple. A specialized form of :class:`Type variable <TypeVar>`
that enables *variadic* generics.

A normal type variable enables parameterization with a single type. A type
variable tuple, in contrast, allows parameterization with an
*arbitrary* number of types by acting like an *arbitrary* number of type
variables wrapped in a tuple. For example::

T = TypeVar('T')
Ts = TypeVarTuple('Ts')

def remove_first_element(tup: tuple[T, *Ts]) -> tuple[*Ts]:
return tup[1:]

# T is bound to int, Ts is bound to ()
# Return value is (), which has type tuple[()]
remove_first_element(tup=(1,))

# T is bound to int, Ts is bound to (str,)
# Return value is ('spam',), which has type tuple[str]
remove_first_element(tup=(1, 'spam'))

# T is bound to int, Ts is bound to (str, float)
# Return value is ('spam', 3.0), which has type tuple[str, float]
remove_first_element(tup=(1, 'spam', 3.0))

Note the use of the unpacking operator ``*`` in ``tuple[T, *Ts]``.
Conceptually, you can think of ``Ts`` as a tuple of type variables
``(T1, T2, ...)``. ``tuple[T, *Ts]`` would then become
``tuple[T, *(T1, T2, ...)]``, which is equivalent to
``tuple[T, T1, T2, ...]``. (Note that in older versions of Python, you might
see this written using :data:`Unpack <Unpack>` instead, as
``Unpack[Ts]``.)

Type variable tuples must *always* be unpacked. This helps distinguish type
variable types from normal type variables::

x: Ts # Not valid
x: tuple[Ts] # Not valid
x: tuple[*Ts] # The correct way to to do it

Type variable tuples can be used in the same contexts as normal type
variables. For example, in class definitions, arguments, and return types::

Shape = TypeVarTuple('Shape')
class Array(Generic[*Shape]):
def __getitem__(self, key: tuple[*Shape]) -> float: ...
def __abs__(self) -> Array[*Shape]: ...
def get_shape(self) -> tuple[*Shape]: ...

Type variable tuples can be happily combined with normal type variables::

DType = TypeVar('DType')

class Array(Generic[DType, *Shape]): # This is fine
pass

class Array2(Generic[*Shape, DType]): # This would also be fine
pass

float_array_1d: Array[float, Height] = Array() # Totally fine
int_array_2d: Array[int, Height, Width] = Array() # Yup, fine too

However, note that at most one type variable tuple may appear in a single
list of type arguments or type parameters::

x: tuple[*Ts, *Ts] # Not valid
class Array(Generic[*Shape, *Shape]): # Not valid
pass

Finally, an unpacked type variable tuple can be used as the type annotation
of ``*args``::

def call_soon(
callback: Callable[[*Ts], None],
*args: *Ts
) -> None:
...
callback(*args)

In contrast to non-unpacked annotations of ``*args`` - e.g. ``*args: int``,
which would specify that *all* arguments are ``int`` - ``*args: *Ts``
enables reference to the types of the *individual* arguments in ``*args``.
Here, this allows us to ensure the types of the ``*args`` passed
to ``call_soon`` match the types of the (positional) arguments of
``callback``.

For more details on type variable tuples, see :pep:`646`.

.. versionadded:: 3.11

.. data:: Unpack

A typing operator that conceptually marks an object as having been
unpacked. For example, using the unpack operator ``*`` on a
:class:`type variable tuple <TypeVarTuple>` is equivalent to using ``Unpack``
to mark the type variable tuple as having been unpacked::

Ts = TypeVarTuple('Ts')
tup: tuple[*Ts]
# Effectively does:
tup: tuple[Unpack[Ts]]

In fact, ``Unpack`` can be used interchangeably with ``*`` in the context
of types. You might see ``Unpack`` being used explicitly in older versions
of Python, where ``*`` couldn't be used in certain places::

# In older versions of Python, TypeVarTuple and Unpack
# are located in the `typing_extensions` backports package.
from typing_extensions import TypeVarTuple, Unpack

Ts = TypeVarTuple('Ts')
tup: tuple[*Ts] # Syntax error on Python <= 3.10!
tup: tuple[Unpack[Ts]] # Semantically equivalent, and backwards-compatible

.. class:: ParamSpec(name, *, bound=None, covariant=False, contravariant=False)

Parameter specification variable. A specialized version of
Expand Down

0 comments on commit 38ae5b8

Please sign in to comment.