需要 **kwargs 的 Callable 类型注释
回答问题
有一个函数 (f) 使用函数签名 (g),该函数签名采用已知的第一组参数和任意数量的关键字参数**kwargs
。有没有办法将**kwargs
包含在 (f) 中描述的 (g) 的类型签名中?
例如:
from typing import Callable, Any
from functools import wraps
import math
def comparator(f: Callable[[Any, Any], bool]) -> Callable[[str], bool]:
@wraps(f)
def wrapper(input_string: str, **kwargs) -> bool:
a, b, *_ = input_string.split(" ")
return f(eval(a), eval(b), **kwargs)
return wrapper
@comparator
def equal(a, b):
return a == b
@comparator
def equal_within(a, b, rel_tol=1e-09, abs_tol=0.0):
return math.isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol)
# All following statements should print `True`
print(equal("1 1") == True)
print(equal("1 2") == False)
print(equal_within("5.0 4.99998", rel_tol=1e-5) == True)
print(equal_within("5.0 4.99998") == False)
函数comparator
用wrapper
包装它的参数f
,它使用f
的输入作为字符串,对其进行解析并使用f
对其进行评估。在这种情况下,Pycharm 会发出警告,指出return f(eval(a), eval(b), **kwargs)
使用与预期签名不匹配的意外参数**kwargs
调用f
。
Reddit 上的这篇帖子建议将Any
或...
添加到f
的类型签名中,例如
-
f: Callable[[Any, Any, ...], bool]
-
f: Callable[[Any, Any, Any], bool]
前者导致 TypeError [1],而后者似乎具有误导性,因为f
接受_至少_2个参数,而不是正好3个。
另一个解决方法是让Callable
像f: Callable[..., bool]
一样打开Callable
args 定义,但我想知道是否有更合适的解决方案。
1.TypeError: Callable[[arg, ...], result]: each arg must be a type. Got Ellipsis.
Answers
tl;dr:Protocol
可能是最接近实现的功能,但它仍然不足以满足您的需求。详见本期。
完整答案:
我认为最接近您所要求的功能是Protocol
,它是在 Python 3.8 中引入的(并通过typing_extensions
向后移植到较旧的 Python)。它允许您定义描述类型行为的Protocol
子类,非常类似于其他语言中的“接口”或“特征”。对于函数,支持类似的语法:
from typing import Protocol
# from typing_extensions import Protocol # if you're using Python 3.6
class MyFunction(Protocol):
def __call__(self, a: Any, b: Any, **kwargs) -> bool: ...
def decorator(func: MyFunction):
...
@decorator # this type-checks
def my_function(a, b, **kwargs) -> bool:
return a == b
在这种情况下,任何具有匹配签名的函数都可以匹配MyFunction
类型。
但是,这不足以满足您的要求。为了使函数签名匹配,函数必须能够接受任意数量的关键字参数(即,具有**kwargs
参数)。到目前为止,仍然无法指定该函数可以(可选地)采用任何关键字参数。这个 GitHub 问题讨论了在当前限制下的一些可能(尽管冗长或复杂)的解决方案。
现在,我建议只使用Callable[..., bool]
作为f
的类型注释。但是,可以使用Protocol
来优化包装器的返回类型:
class ReturnFunc(Protocol):
def __call__(self, s: str, **kwargs) -> bool: ...
def comparator(f: Callable[..., bool]) -> ReturnFunc:
....
这消除了equal_within("5.0 4.99998", rel_tol=1e-5)
的“意外关键字参数”错误。
更多推荐
所有评论(0)