Let's say I have code like this:
def a(n:int = 10, s:str = "")->int:
return n
def b(**args):
return a(**args)
Is there any way to tell python that b takes one argument named n that is an integer and another s that is a string? In other words, is there a way to pass the typing through **dict?
Update: to be clear, I am looking for a general solution that would work for arbitrary number of arguments with arbitrary types. My goal is to follow the DRY principle when it comes to specifying argument types.
This is an old story, which was discussed for a long time. Here's mypy issue related to this problem (still open since 2018), it is even mentioned in PEP-589. Some steps were taken in right direction: python 3.11 introduced Unpack and allowed star unpacking in annotations - it was designed together with variadic generics support, see PEP-646 (backported to typing_extensions, but no mypy support yet AFAIC). But it works only for *args, **kwargs construction is still waiting.
However, it is possible with additional efforts. You can create your own decorator that can convince mypy that function has expected signature (playground):
from typing import Any, Callable, TypeVar, cast
_C = TypeVar('_C', bound=Callable)
def preserve_sig(func: _C) -> Callable[[Callable], _C]:
def wrapper(f: Callable) -> _C:
return cast(_C, f)
return wrapper
def f(x: int, y: str = 'foo') -> int:
return 1
@preserve_sig(f)
def g(**kwargs: Any) -> int:
return f(**kwargs)
g(x=1, y='bar')
g(z=0) # E: Unexpected keyword argument "z" for "g"
You can even alter function signature, appending or prepending new arguments, with PEP-612 (playground:
from functools import wraps
from typing import Any, Callable, Concatenate, ParamSpec, TypeVar, cast
_R = TypeVar('_R')
_P = ParamSpec('_P')
def alter_sig(func: Callable[_P, _R]) -> Callable[[Callable], Callable[Concatenate[int, _P], _R]]:
def wrapper(f: Callable) -> Callable[Concatenate[int, _P], _R]:
@wraps(f)
def inner(num: int, *args: _P.args, **kwargs: _P.kwargs):
print(num)
return f(*args, **kwargs)
return inner
return wrapper
def f(x: int, y: str = 'foo') -> int:
return 1
@alter_sig(f)
def g(**kwargs: Any) -> int:
return f(**kwargs)
g(1, x=1, y='bar')
g(1, 2, 'bar')
g(1, 2)
g(x=1, y='bar') # E: Too few arguments for "g"
g(1, 'baz') # E: Argument 2 to "g" has incompatible type "str"; expected "int"
g(z=0) # E: Unexpected keyword argument "z" for "g"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With