回答问题

有一个函数 (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)

函数comparatorwrapper包装它的参数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个。

另一个解决方法是让Callablef: Callable[..., bool]一样打开Callableargs 定义,但我想知道是否有更合适的解决方案。

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)的“意外关键字参数”错误。

Logo

学AI,认准AI Studio!GPU算力,限时免费领,邀请好友解锁更多惊喜福利 >>>

更多推荐