Passing a value to a generic function argument T
would make T
be resolved to the type of the passed value.
def func(*args: *Ts) -> tuple[*Ts]: ...
func(1, 'a') # tuple[int, str]
func(int, str) # tuple[type[int], type[str]]
Before type transformations on variadic generics are implemented in Python, I could think of some workarounds. Basically, the only place I can think of where a type T
would be matched as expected (e.g. int
instead of Type[int]
) is when T
is passed as a type argument for a generic class.
class Args(Generic[*Ts]):
def fn(self) -> tuple[*Ts]: ...
Args[int, str]().fn() # tuple[int, str]
The following are workarounds employing this idea.
TypeArgs workaround
Definition
class Args(Generic[*Ts]): pass
TypeArgs = type[Args[*Ts]]
Usage
def qux(a: TypeArgs[*Ts]) -> tuple[*Ts]:
...
d = qux(Args[int, str])
Example
from typing import Generic, TypeVar, TypeVarTuple, reveal_type
T = TypeVar("T")
Ts = TypeVarTuple("Ts")
# What syntax?
class Args(Generic[*Ts]): pass
TypeArgs = type[Args[*Ts]]
def qux(a: TypeArgs[*Ts]) -> tuple[*Ts]: # instead of def qux(*a: type[*Ts]) -> tuple[*Ts]:
...
d = qux(Args[int, str]) # instead of d = qux(int, str)
reveal_type(d) # should be tuple[int, str]
Callable objects workaround
Converting a function definition into a callable object.
Definition
class qux(Generic[*Ts]):
def __call__(self) -> tuple[*Ts]:
...
Usage
d = qux[int, str]()()
Example
from typing import Generic, TypeVar, TypeVarTuple, reveal_type
T = TypeVar("T")
Ts = TypeVarTuple("Ts")
# What syntax?
# instead of def qux(*a: type[*Ts]) -> tuple[*Ts]:
class qux(Generic[*Ts]):
def __call__(self) -> tuple[*Ts]:
...
d = qux[int, str]()() # instead of d = qux(int, str)
reveal_type(d) # should be tuple[int, str]
Tuple extension workaround
Definition
class qux(Generic[*Ts], tuple[*Ts]):
def __new__(cls) -> qux[*Ts]:
...
Usage
d: tuple[int, str] = qux[int, str]()
Example
from __future__ import annotations
from typing import Generic, TypeVar, TypeVarTuple, reveal_type
T = TypeVar("T")
Ts = TypeVarTuple("Ts")
def with_tuple(t: tuple[int, str]): ...
# What syntax?
# instead of def qux(*a: type[*Ts]) -> tuple[*Ts]:
class qux(Generic[*Ts], tuple[*Ts]):
def __new__(cls) -> qux[*Ts]:
...
# instead of d = qux(int, str)
d: tuple[int, str] = qux[int, str]() # Accepted
reveal_type(d) # should be tuple[int, str]
with_tuple(qux[int, str]()) # Accepted