Python 运行时类型检查传递给装饰器的函数是否具有正确的签名

Python runtime type check that function passed to a decorator has correct signature

我有一个 python 装饰器,它期望传递函数的签名是 UserPoint,它 returns 是一个具有相同功能的函数签名:

@dataclass
class User:
    name: str

@dataclass
class Point:
    x: float
    y: float
    z: float


def logging_decorator(f: Callable[[User, Point], bool]) -> Callable[[User, Point], bool]:
    print('decorating function...')
    def decorated(a: User, b: Point) -> bool:
        print(f'User {a.name} calling with points {b.x},{b.y},{b.z}')
        ret = f(a, b)
        print(f'returned {ret}')
        return ret
    return decorated

在打印 decorating function... 时,我希望它检查传入的函数是否具有正确的签名,如果不正确,则给出合理的错误消息。

这里是装饰两个函数的例子,一个签名正确,一个签名不正确:

@logging_decorator
def good_f(a: User, b: Point) -> bool:
    return True

@logging_decorator
def bad_f(a: str, b: str) -> str:
    return "Hello"

如何向 logging_decorator 添加检查,以便检查被修饰的函数是否具有正确的参数类型?

我尝试使用 typing_inspecttypeguard 包,但找不到我需要的功能。

我正在使用 Python 3.8.3

使用标准库中的inspect模块。使用 inspect.signature,您可以编写如下函数验证代码:

import inspect

def validate_func(func):
    def good_func(a: User, b: Point) -> bool: ...
    good_func_signature = inspect.signature(good_func)
    received_signature = inspect.signature(func)
    if received_signature != good_func_signature:
        raise TypeError(
            f'Invalid function signature: '
            f'expected "{good_func_signature}", '
            f'got "{received_signature}"'
        )

(您可以将此逻辑放入您的用例的装饰器中;我只是将它写为一个单独的函数,以使其与您的代码分开。)

使用中:

>>> validate_func(good_f)
>>> validate_func(lambda a, b: a)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "<string>", line 6, in validate_func
TypeError: Invalid function signature: expected "(a:__main__.User, b:__main__.Point) -> bool", got "(a, b)"
>>> validate_func(bad_f)
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "<string>", line 6, in validate_func
TypeError: Invalid function signature: expected "(a:__main__.User, b:__main__.Point) -> bool", got "(a:str, b:str) -> str"

如果需要,您可以在签名之间进行更详细和更细粒度的比较,而不仅仅是简单的相等性检查。您可以使用 inspect.signature 中的 return 值进行比较:

  • 参数个数
  • 参数名称
  • 参数的种类(仅位置、位置或关键字或仅关键字)
  • 参数是否有注解
  • 参数的注解是什么
  • 参数是否有默认值
  • 参数的默认值是什么
  • 函数是否有return注解
  • 函数的return注解是什么

您可以找到 inspect.signature here 的文档。