有一个函数 (
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
包装其参数
f
,它将
wrapper
的输入作为字符串使用,解析它并使用
f
对其求值。在这种情况下,Pycharm 会发出警告,指出
f
使用意外参数
return f(eval(a), eval(b), **kwargs)
调用
f
,该参数与预期签名不匹配。
**kwargs
Reddit 上的这篇文章
建议添加
或
Any
到
...
的类型签名类似
f
前者会导致 TypeError [1],而后者似乎具有误导性,因为
-
f: Callable[[Any, Any, ...], bool]
-
f: Callable[[Any, Any, Any], bool]
至少接受
f
2 个参数,而不是完全接受3.
另一种解决方法是使用
like
打开
Callable
args定义,但我想知道是否有更合适的解决方案。
...
like
f: Callable[..., bool]
, but I'm wondering if there is a more appropriate solution.
-
TypeError: Callable[[arg, ...], result]: each arg must be a type. Got Ellipsis.
你遇到的问题在于,你试图为一个可以接受任意数量参数的函数定义一个精确的类型签名。虽然
Callable[[Any, Any], bool]
适用于像
equal
这样的简单函数,但它无法处理
equal_within
等函数需要的额外
**kwargs
。
你已经考虑过使用
Callable[..., bool]
,但正如你所发现的那样,Python 的类型提示还不直接支持这种用法。
那么,你有什么选择呢?
-
使用
typing.ParamSpec
(Python 3.10+)
ParamSpec
允许你捕获和转发可调用对象的参数。
```python from typing import Callable, ParamSpec, TypeVar, Concatenate
P = ParamSpec("P") R = TypeVar("R")
def comparator(f: Callable[Concatenate[int, int, P], R]) -> Callable[[str, P], R]: @wraps(f) def wrapper(input_string: str, args: P.args, kwargs: P.kwargs) -> R: a, b, _ = input_string.split(" ") return f(int(a), int(b), args, *kwargs) return wrapper
@comparator def equal(a: int, b: int) -> bool: return a == b
@comparator def equal_within(a: int, b: int, rel_tol: float = 1e-09, abs_tol: float = 0.0) -> bool: return math.isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol) ```
在这个例子中:
-
P = ParamSpec("P")
定义了一个新的参数规范P
。 -
Concatenate[int, int, P]
表示函数f
接受两个 int 参数,后面跟着由P
捕获的任意参数。 -
wrapper
函数现在使用*args: P.args
和**kwargs: P.kwargs
来接受并传递任意数量的参数。 -
使用
Callable[[Any, ...], bool]
这是最实际的解决方案。虽然它不像
ParamSpec
那样精确,但它能传达函数接受两个或多个参数并返回一个布尔值的信息。
```python from typing import Callable, Any
def comparator(f: Callable[[Any, ...], bool]) -> Callable[[str], bool]: # ... your code ... ```
- 忽略类型提示
在某些情况下,如果类型提示带来的复杂性超过了它们提供的价值,你可以选择忽略它们。但这通常不应该是首选的解决方案。
总的来说,对于你的用例,使用
Callable[[Any, ...], bool]
是一种很好的折衷方案,它在可读性和类型安全性之间取得了平衡。如果你的 Python 版本 >= 3.10,并且需要更精确的类型信息传递,那么
ParamSpec
是一个更强大的工具。