EZLearner
EZLearner

Reputation: 1834

Python: TypeVar for multiple arguments at once

Is there a way to have a TypeVar (or some other format) capture all the arguments of a function? For example, say I want to wrap a general function, such that all its arguments are given within a tuple:

def invoke(f: Callable[..., T], args: Tuple[...]) -> T:
    return f(*args)

Only that instead of the ellipsis (...), I'll have the static-type inspection enforce the contents of the Tuple to be have the same types as the function's arguments.

Thanks.

Upvotes: 6

Views: 1174

Answers (3)

Unmitigated
Unmitigated

Reputation: 89139

Since Python 3.12, the syntax is simplified with type parameter lists so that it is no longer necessary to explicitly use TypeVar or ParamSpec here.

from collections.abc import Callable


def invoke[R, **P](f: Callable[P, R], *args: P.args, **kwargs: P.kwargs) -> R:
    return f(*args, **kwargs)

Upvotes: 1

EZLearner
EZLearner

Reputation: 1834

The answer is Yes, just not with TypeVar. One should use ParamSpec:

from typing import ParamSpec, TypeVar, Callable
P = ParamSpec('P')
RT = TypeVar('RT')

def invoke(f: Callable[P, RT], args: P.args) -> RT:
    return f(*args)

# Or, makes more sense:

def invoke(f: Callable[P, RT], args: P.args, **kwargs: P.kwargs) -> RT:
    return f(*args, **kwargs)

Note that for python < 3.10, one should import it from typing_extensions rather than typing.

Upvotes: 2

Lucan
Lucan

Reputation: 3585

You could modify the below to work with your requirements, you'll likely need to add in extra handling.

from typing import Any

class TypeEnforce:
    def __init__(self, func):
        self.func = func
    def __call__(self, *args):
        types = dict(zip(self.func.__annotations__.values(), args))
        for k, v in types.items():
            if k is Any:
                continue
            assert type(v) == k
        self.func(*args)

Example

@TypeEnforce
def my_test(x: str, y: int) -> None:
    print(f"'x' is a {type(x).__name__}")


@TypeEnforce
def my_other_test(x: Any):
    return x

my_test("Test", "eight")
my_other_test("Test2")

Will result in an AssertionError because the function my_test takes (str, int) but is passed (str, str). There would also be edge cases where a hint of TypedDict would always fail because it isn't really a type, rather syntactic sugar for a dict.

Upvotes: 1

Related Questions