Python variadic generics accepting class
Asked Answered
G

1

6

a variable annotated with Type[C] may accept values that are classes themselves

How do you do this with TypeVarTuples (PEP 646)

from typing import TypeVar, TypeVarTuple

T = TypeVar("T")
Ts = TypeVarTuple("Ts")

def foo(a: T) -> T:
    ...
def bar(a: type[T]) -> T:
    ...
def baz(*a: *Ts) -> tuple[*Ts]:
    ...

a = foo(int) # type[int]
b = bar(int) # int
c = baz(int, str) # tuple[type[int], type[str]]

# What syntax?
def qux(*a: type[*Ts]) -> tuple[*Ts]: 
    ...
d = qux(int, str) # should be tuple[int, str]
Groundsill answered 1/5, 2023 at 23:55 Comment(2)
@Axe319 Sorry yes I've updated the questionGroundsill
Possibly related github issue.Jail
C
0

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

Chilopod answered 7/8, 2024 at 21:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.