bpo-47097: Add documentation for TypeVarTuple (#32103)
https://github.com/python/cpython/commit/38ae5b8c0c0b64ae6100b0dee8707d5ab76... commit: 38ae5b8c0c0b64ae6100b0dee8707d5ab769e381 branch: main author: Matthew Rahtz <matthew.rahtz@gmail.com> committer: JelleZijlstra <jelle.zijlstra@gmail.com> date: 2022-04-04T16:37:01-07:00 summary: bpo-47097: Add documentation for TypeVarTuple (#32103) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> files: M Doc/library/typing.rst diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 4d833dc497f17..37c17c429fa47 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -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 @@ -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
participants (1)
-
JelleZijlstra