1

I am trying to annotate a function which receives a tuple of types, and returns a value of one of the provided types.

For example:

@overload
def func[T1](types: tuple[type[T1]]) -> T1: ...
@overload
def func[T1, T2](types: tuple[type[T1], type[T2]]) -> T1 | T2: ...
@overload
def func[T1, T2, T3](types: tuple[type[T1], type[T2], type[T3]]) -> T1 | T2 | T3: ...

Conceptually analogous to the following invalid code:

# invalid
def func[*Ts](types: tuple[type[*Ts]]) -> Union[*Ts]: ...

Is something like this supported?

4
  • Not directly: github.com/python/typing/discussions/1824 Commented Oct 2 at 19:15
  • Not directly relevant to the problem, but should it really be tuple and not collections.abc.Collection? There's rarely a reason not to allow any kind of collection. Commented Oct 2 at 20:58
  • @Barmar tuple captures each distinct member in the variadic pack, which is the point of the question, whereas most other kinds of type annotation don't. Commented Oct 3 at 5:28
  • Right. After I posted that I realized that Collection is for collections of homogeneous types. Commented Oct 3 at 14:18

2 Answers 2

2

Depending on what type checker you use, something like this might work:

(playground: Mypy, Pyright, Pyrefly, ty)

# Either
def func[T](types: tuple[type[T], ...]) -> T: ...
# or
def func[T](types: Iterable[type[T]]) -> T: ...
reveal_type(f((int,)))      # mypy, pyright => int
reveal_type(f((int, str)))  # mypy          => object
                            # pyright       => int | str

# Pyrefly errors out; ty infers Unknown.

This works due to a property of type[]: It distributes over unions. That is, type[A] | type[B] is equivalent to type[A | B].

A type checker might infer the type tuple[type[int], type[str]] for (int, str) and then cast it to tuple[type[int | str], ...] or Iterable[type[int | str]], which leads to T being resolved as int | str. This behaviour is not guaranteed across type checkers, however, as seen above.

Sign up to request clarification or add additional context in comments.

1 Comment

This looks very promising, thanks for sharing. Getting object from mypy is a sad situation but better than nothing.
-1

I'd be most tempted to use the ellipsis and avoid bothering to associate the length of the tuple with the result length

def func(tuples: Tuple[T, ...]) -> Tuple[T, ...]:

See also my Answer here Pydantic issue for tuple length

4 Comments

The return value isn't a tuple, it's an object of one of the types in the input tuple.
An example would be something like random.choice()
sheesh, indeed - I have trouble reading when my @overload allergy flares up..
I also missed that the elements are type[Tn] rather than just Tn on my first reading. So it's a function that takes a collection of types, and returns an instance of one of those types.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.