从 **args 推断参数类型
Inferring argument types from **args
假设我有这样的代码:
def a(n:int = 10, s:str = "")->int:
return n
def b(**args):
return a(**args)
有什么方法可以告诉 python b
接受一个名为 n
的整数参数和另一个名为 s
的字符串参数?换句话说,有没有办法通过**dict传递输入?
更新:明确地说,我正在寻找一种适用于任意数量和任意类型的参数的通用解决方案。我的目标是在指定参数类型时遵循 DRY 原则。
这是一个古老的故事,讨论了很长时间。这是 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(向后移植到 typing_extensions
,但尚无 mypy
支持 AFAIC)。但只对*args
有效,**kwargs
建设还在等待
但是,通过额外的努力是可能的。您可以创建自己的装饰器来说服 mypy
该函数具有预期的签名 (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"
您甚至可以更改函数签名,附加或前置新参数,使用 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"
假设我有这样的代码:
def a(n:int = 10, s:str = "")->int:
return n
def b(**args):
return a(**args)
有什么方法可以告诉 python b
接受一个名为 n
的整数参数和另一个名为 s
的字符串参数?换句话说,有没有办法通过**dict传递输入?
更新:明确地说,我正在寻找一种适用于任意数量和任意类型的参数的通用解决方案。我的目标是在指定参数类型时遵循 DRY 原则。
这是一个古老的故事,讨论了很长时间。这是 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(向后移植到 typing_extensions
,但尚无 mypy
支持 AFAIC)。但只对*args
有效,**kwargs
建设还在等待
但是,通过额外的努力是可能的。您可以创建自己的装饰器来说服 mypy
该函数具有预期的签名 (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"
您甚至可以更改函数签名,附加或前置新参数,使用 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"