diff --git a/pep-0646.rst b/pep-0646.rst index 7cf08d45619..e48b985f1e0 100644 --- a/pep-0646.rst +++ b/pep-0646.rst @@ -414,16 +414,25 @@ Normal ``TypeVar`` instances can also be prefixed and/or suffixed: z = prefix_tuple(x=0, y=(True, 'a')) # Inferred type of z is Tuple[int, bool, str] -``*args`` as a Type Variable Tuple ----------------------------------- +Heterogeneous ``*args`` Types +----------------------------- PEP 484 states that when a type annotation is provided for ``*args``, every argument must be of the type annotated. That is, if we specify ``*args`` to be type ``int``, then *all* arguments must be of type ``int``. This limits our ability to specify the type signatures of functions that take heterogeneous argument types. -If ``*args`` is annotated as a type variable tuple, however, the types of the -individual arguments become the types in the type variable tuple: +With the new use for the star operator introduced in this PEP, however, we +can do better. In short, if ``*args`` is annotated as a star-prefixed type +tuple or type variable tuple, the types of the individual arguments are bound +to the types within the star-prefixed container. + +Using a Type Variable Tuple +''''''''''''''''''''''''''' + +For example, annotating ``*args`` as a star-prefixed type variable tuple +results in the types in the type variable tuple being bound to the types +of the individual arguments: :: @@ -446,8 +455,47 @@ instance is *not* allowed: ``*args`` is the only case where an argument can be annotated as ``*Ts`` directly; other arguments should use ``*Ts`` to parameterise something else, e.g. ``Tuple[*Ts]``. -If ``*args`` itself is annotated as ``Tuple[*Ts]``, the old behaviour still applies: -all arguments must be a ``Tuple`` parameterised with the same types. + +Also note that, following `Type Variable Tuples Must Have Known Length`_, +the following should *not* type-check as valid (even though it is, of +course, valid at runtime): + +:: + + def foo(*args: *Ts): ... + + def bar(x: Tuple[int, ...]): + foo(*x) # NOT valid + +Using a Tuple of Types +'''''''''''''''''''''' + +Similarly, ``*args`` can be annotated as a star-prefixed tuple type: + +:: + + def foo(*args: *Tuple[int, int]): ... + +This is very similar to: + +:: + + def bar(arg1: int, arg2: int): ... + +However, ``bar`` is subtly different than ``foo``: a) ``foo`` may be more +convenient if e.g. passing ``args`` on to something else, and b) ``foo`` +disallows keyword arguments. + +Note that arbitrary-length tuple types can also be used here: + +:: + + def foo(*args: *Tuple[int, ...]): ... + +This is roughly the same as ``*args: int``. + +Also note that if ``*args`` is annotated as an *unstarred* ``Tuple[*Ts]``, the +old ``*args`` behaviour still applies: all arguments must be a ``Tuple`` parameterised with the same types. :: @@ -457,18 +505,31 @@ all arguments must be a ``Tuple`` parameterised with the same types. foo((0,), (1, 2)) # Error foo((0,), ('1',)) # Error -Following `Type Variable Tuples Must Have Known Length`_, note -that the following should *not* type-check as valid (even though it is, of -course, valid at runtime): +Using a Type Variable Tuple Inside a Tuple +'''''''''''''''''''''''''''''''''''''''''' + +Finally, by using a type variable tuple inside a tuple of other +types, we can refer to prefixes or suffixes of the variadic +argument list. For example: :: - def foo(*args: *Ts): ... + # os.execle takes arguments 'path, arg0, arg1, ..., env' + def execle(path: str, *args: *Tuple[*Ts, Mapping[str, str]]) → None: ... - def bar(x: Tuple[int, ...]): - foo(*x) # NOT valid +Note this this is different to + +:: + + def execle(path: str, *args: *Ts, env: Mapping[str, str]) → None: ... + +as this would make ``env`` a keyword-only argument. + +What About ``**kwargs?`` +'''''''''''''''''''''''' -Finally, note that a type variable tuple may *not* be used as the type of +The PEP does *not* introduce any new behaviour for typing ``**kwargs``. +In particular, a type variable tuple may *not* be used as the type of ``**kwargs``. (We do not yet know of a use case for this feature, so we prefer to leave the ground fresh for a potential future PEP.)