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"