Working with type stubs, I’m wondering if it’s possible to express a type in Python that allows you to type this correctly for any number of arguments:
def test(*args): return args
At first glance, I came with:
T = TypeVar('T') def test(*args: T) -> Tuple[T, ...]: return args
But this of course will only type correctly the first T.
Is the only possible way to write the overrides for all arities?
T1 = TypeVar('T1') T2 = TypeVar('T2') T3 = TypeVar('T3') T4 = TypeVar('T4') @overload def test(arg1: T1) -> Tuple[T1]: ... @overload def test(arg1: T1, arg2: T2) -> Tuple[T1, T2]: ... @overload def test(arg1: T1, arg2: T2, arg3: T3) -> Tuple[T1, T2, T3]: ... @overload def test(arg1: T1, arg2: T2, arg3: T3, arg4: T4) -> Tuple[T1, T2, T3, T4]: ... # etc def test(*args: Any) -> Tuple[Any, ...]: return args
This is not complete either, since it does not carry enough type information to type something like:
x: Tuple[int, int, str] = test(*[1, 2, "4"])
Advertisement
Answer
This can solved through TypeVarTuple
from PEP646, implemented in Python 3.11 or forward compat module typing-extensions.
See this analog example:
from __future__ import annotations from typing import Any, Callable, Generic from typing_extensions import TypeVarTuple, Unpack # typing_extensions only needed for Python < 3.11 Ts = TypeVarTuple("Ts") class Signal(Generic[Unpack[Ts]]): def add_callback(self, func: Callable[[Unpack[Ts]], Any]) -> None: ... def emit(self, *args: Unpack[Ts]) -> None: ... def callback(a: int, b: str) -> None: ... def callback_bad(a: str) -> None: ... sig: Signal[int, str] = Signal() sig.add_callback(callback) # Good lint sig.add_callback(callback_bad) # Bad lint sig.emit(1223, "foo") # Good lint sig.emit("bar") # Bad lint
Also see Dynamic TypeVar for a sequence of types for a solution.