Skip to content

bpo-47097: Add documentation for TypeVarTuple #32103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 4, 2022
Merged
99 changes: 99 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,103 @@ 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

:class:`Type variable <TypeVar>` tuple.

A vanilla 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::

Shape = TypeVarTuple('Shape')

class Array(Generic[*Shape]): ...

array_1d: Array[int] = Array() # 1 type parameter: fine
array_2d: Array[int, int] = Array() # 2 type parameters: also fine
array_0d: Array[()] = Array() # 0 type parameters: also fine

Note the use of the unpacking operator ``*`` in ``Generic[*Shape]``.
Conceptually, you can think of ``Shape`` as a tuple of type variables
``(T1, T2, ...)``. ``Generic[*Shape]`` would then become
``Generic[*(T1, T2, ...)]``. The ``*`` brings the type variables out of the
tuple and into the square brackets directly.

(In older versions of Python, where ``*`` was less flexible, you might see
this written using :data:`Unpack <Unpack>` instead, as
``Unpack[Shape]``. This means the same thing as ``*Shape`` - ``Unpack`` and
``*`` can be used interchangeably in the context of types.)


Type variable tuples must *always* be used in combination with the unpacking
operator. This helps distinguish type variable types from normal type
variables::

class Array(Generic[Shape]): ... # Not valid

In cases where you really do just want a tuple of types, make this explicit
by wrapping the type variable tuple in ``tuple[]``::

shape: Shape # Not valid
shape: tuple[Shape] # Also not valid
shape: tuple[*Shape] # The correct way to do it

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

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 vanilla type variables::

DType = TypeVar('DType')

class Array(Generic[DType, *Shape]): # This is 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::

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

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

def args_to_tuple(*args: *Ts) -> tuple[*Ts]:
return tuple(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``.
In this case, it binds those types to ``Ts``.

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>` internally uses ``Unpack``
to mark the type variable tuple as having been unpacked::

Ts = TypeVarTuple('Ts')
array: tuple[*Ts]
# Effectively does:
array: 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.

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

Parameter specification variable. A specialized version of
Expand Down