I have a typing and API design question.
With non-variadic generics, I can write something like the following:
from typing import Generic, Type, TypeVar
T = TypeVar("T")
class Example(Generic[T]):
atype: Type[T]
def __init__(self, atype: Type[T]):
self.atype = atype
def get_value(self) -> T:
# I can use self.atype in here
...
Then later when instantiating Example
, a type checker can infer T based on the argument passed to the initializer:
example = Example(int)
n = example.get_value() # inferred as 'int'
I’ve used this to do things like parsing and validation of text when I want the parsed value to be typed correctly.
Recently, I was experimenting with something similar for multiple values. Homogenous collections are pretty simple since you can parameterize on the element type:
class SeqExample(Generic[T]):
element_type: Type[T]
def __init__(self, etype: Type[T]):
self.element_type = etype
def get_value(self) -> list[T]:
...
list_example = SeqExample(int)
ns = list_example.get_value() # inferred as 'list[int]'
I was experimenting with writing a version for fixed-length tuples, imagining a usage like this:
tup_example = TupExample(int, str, bool)
n, s, b = tup_example.get_value() # inferred as `tuple[int, str, bool]`
This led me to TypeVarTuple
, which I had not used before and found very cool, since I could get as far as this:
Ts = TypeVarTuple("Ts")
class TupExample(Generic[*Ts]):
# ...?
def get_value(self) -> tuple[*Ts]:
...
My questions are:
- Is even possible to do something like what I do with
T
/Type[T]
withTypeVarTuple
? (I am thinking not, but I want to know for sure) - Does anyone have ideas or suggestions for implementing the kind of API I’m interested in? I can easily enough implement an un-hinted version by storing
tuple[type, ...]
, but then I lose inference of the method return type based on the arguments to__init__
and have to specify the member types twice, once for the generic parameters and once for the init parameters:
Ts = TypeVarTuple("Ts")
class TupExample(Generic[*Ts]):
element_types: tuple[type, ...]
def __init__(self, *etypes: type):
self.element_types = etypes
def get_value(self) -> tuple[*Ts]:
...
# duplication suboptimal...
tup_example = TupExample[int, str, bool](int, str, bool)
n, s, b = tup_example.get_value() # inferred `tuple[int, str, bool]`, yay!