Python Typing: declare return value type based on function argument

Suppose I have function that takes type as argument and returns instance of that type:

def fun(t):
    return t(42)

Then I can call it and get objects of provided types:

fun(int)           # 42
fun(float)         # 42.0
fun(complex)       # (42+0j)
fun(str)           # "42"
fun(MyCustomType)  # something

That list is not exhaustive, I'd like to be able to use any type with appropriate constructor.

Then, I'd like to add type hints for that function. What should be the type hint for return value of that function?


I've tried using simply t, as t is a type:

def fun(t: type) -> t:
    return t(42)

but that doesn't work:

main.py:1: error: Name 't' is not defined


This answer suggests using a TypeVar:

from typing import TypeVar

T = TypeVar("T")

def fun(t: T) -> T:
    return t(42)

But that doesn't seem to be right, as T denotes a type, so it suggests that type itself is returned, not its instance. Mypy rejects it:

main.py:6: error: "object" not callable


Using Any obviously work, but I feel it's too vague, it doesn't convey the intent:

from typing import Any

def fun(t: type) -> Any:
    return t(42)

Upvotes: 23

Views: 31602

Answers (3)

Anton Danylchenko
Anton Danylchenko

Reputation: 2358

You already have answer above, but I will repeat it:

def fun(t: type[T]) -> T:
  return t(42)

The type can be used since Python 3.10 I think. Before it was Type.

You probably wan something like this (to convert any value to the given type):

def fun(t: type[T], v) -> T:
  return t(v)

Upvotes: 1

MisterMiyagi
MisterMiyagi

Reputation: 50066

TLDR: You need a TypeVar for the return type of calling t:

def fun(t: Callable[[int], R]) -> R:
    ...

Constraining on a type is too restrictive here. The function accepts any Callable that takes an integer, and the return type of the function is that of the Callable. This can be specified using a TypeVar for the return type:

from typing import Callable, TypeVar


R = TypeVar('R')  # the variable return type


def fun(t: Callable[[int], R]) -> R:
    return t(42)

fun(int)                            # Revealed type is 'builtins.int*'
fun(float)                          # Revealed type is 'builtins.float*'
reveal_type(fun(lambda x: str(x)))  # Revealed type is 'builtins.str*'

This works for types as well, because type instantiation is a call.

If a more complex signature, e.g. with keyword arguments, is needed, use Protocol (from typing or typing_extensions).


Note that if one explicitly wants to pass only 42 to the Callable, Literal (from typing or typing_extensions) can be used to specify that.

R = TypeVar('R')


def fun(t: Callable[[Literal[42]], R]) -> R:
    return t(42)

Note that any function of the type Callable[[int], R] also satisfies Callable[[Literal[42]], R].

Upvotes: 16

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 95863

You are looking for typing.Type, so something to the effect of:

from typing import TypeVar, Type

T = TypeVar("T", str, complex, float, int)

def fun(t: Type[T]) -> T:
    return t(42)

fun(int)
fun(float)
fun(complex)
fun(str)

Note, your type variable needs to be constrained, because not all Type objects accept arguments, but you can constrain it to a few that do like your example.

Upvotes: 11

Related Questions