I would like to code a wrapper that takes in arguments a function, its args and kwargs and execute them but with some kwargs precisely known while others are unknown.
Exemple:
def wrapper(custom_function: MyType, a: int, b: str, *args, **kwargs) -> float:
print(a+3)
print(b)
return custom_function(a, b, *args, **kwargs)
In this exemple I want to execute any function with first argument named a
with type int
and second named b with type str
but I don't care about any other argument.
With this wrapper, I would like the following type hints to succeed or fail:
def f1(a: int, b: str) -> float:
...
def f2(a: int, b: str, c: float) -> float:
...
def f3(a: str, b: str) -> float:
...
def f4(a: int, b: str, *args, **kwargs) -> float:
...
wrapper(f1, 1, "a") # test 1: succeed
wrapper(f2, 1, "a", 4.6) # test 2: succeed
wrapper(f3, 1, "a") # test 3: fail
wrapper(f4, 1, "a", [1, 2, 3]) # test 4: succeed
I tried using typing.Protocol
and typing.ParamSpec
in the following way:
P = ParamSpec("P")
class MyType(Protocol):
def __call__(self, a: int, b: str, P):
....
but it does not work (test 1 and 2 fail)
I guess using Callable[...]
like this would be the nearest I can get:
MyType = Callable[..., float]
but this solution does not satisfy me since the test 3 would succeed while I would like it to fail.
Is what I am looking for impossible ?
I would like to code a wrapper that takes in arguments a function, its args and kwargs and execute them but with some kwargs precisely known while others are unknown.
This is exactly the job for typing.Concatenate
. Your example is a modified version of the typing
docs example, but instead of returning a wrapped custom_function
with a modified signature, your example just invokes custom_function
directly.
from typing import Callable, Concatenate, ParamSpec, TypeVar
P = ParamSpec("P")
R = TypeVar("R")
def wrapper(custom_function: Callable[Concatenate[int, str, P], R], a: int, b: str, *args: P.args, **kwargs: P.kwargs) -> R:
print(a+3)
print(b)
return custom_function(a, b, *args, **kwargs)
def f1(a: int, b: str) -> float:
...
def f2(a: int, b: str, c: float) -> float:
...
def f3(a: str, b: str) -> float:
...
def f4(a: int, b: str, *args, **kwargs) -> float:
...
wrapper(f1, 1, "a") # OK
wrapper(f2, 1, "a", 4.6) # OK
wrapper(f3, 1, "a") # "int" is incompatible with "str"
wrapper(f4, 1, "a", [1, 2, 3]) # OK