如何实现支持带参Callable与无参Callable的通用装饰器并通过类型检查?
如何实现支持带参Callable与无参Callable的通用装饰器并通过类型检查?
我完全懂你的需求——想搞一个全能的装饰器deco3,既能适配和被装饰函数签名完全匹配的带参回调,也能兼容无参的回调函数,同时还要让PyRight这类类型检查器不报错。你现在的代码运行没问题,但类型检查通不过,核心原因是类型检查器没办法自动区分fn的两种不同Callable类型,得给它明确的“分支提示”才行。
解决这个问题的最佳方案是使用函数重载(@overload),通过定义不同的函数签名,让类型检查器根据传入的fn类型,匹配对应的处理逻辑。下面是具体的实现:
import inspect from typing import Any, Callable, ParamSpec, TypeVar, overload P = ParamSpec("P") T = TypeVar("T") # 重载1:处理无参的回调函数 @overload def deco3(fn: Callable[[], Any]) -> Callable[[Callable[P, T]], Callable[P, T]]: ... # 重载2:处理和被装饰函数签名一致的带参回调 @overload def deco3(fn: Callable[P, Any]) -> Callable[[Callable[P, T]], Callable[P, T]]: ... # 实际实现逻辑 def deco3( fn: Callable[[], Any] | Callable[P, Any], ) -> Callable[[Callable[P, T]], Callable[P, T]]: def decorator(wrapped_fn: Callable[P, T]) -> Callable[P, T]: def inner(*args: P.args, **kwargs: P.kwargs) -> T: # 运行时判断回调是否需要参数 if len(inspect.signature(fn).parameters) > 0: # 类型检查器会根据重载推断fn是Callable[P, Any] fn(*args, **kwargs) else: # 类型检查器会推断fn是Callable[[], Any] fn() return wrapped_fn(*args, **kwargs) return inner return decorator # 测试用的回调函数 def logger1(x: int, y: str) -> None: print(f"logger1: x={x}, y={y}") def logger2() -> None: print(f"logger2") # 测试装饰器 @deco3(logger1) def wrapped_with_logger1(x: int, y: str) -> None: ... @deco3(logger2) def wrapped_with_logger2(x: int, y: str) -> None: ... # 调用测试,类型检查器不会报错 wrapped_with_logger1(1, "test") # 输出: logger1: x=1, y=test wrapped_with_logger2(1, "test") # 输出: logger2
为什么这个方案能解决问题?
- 我们通过
@overload给deco3定义了两个明确的签名:- 当传入的
fn是无参函数时,装饰器可以接受任意签名的被装饰函数,因为无参回调不需要传递参数。 - 当传入的
fn是带参函数时,装饰器要求被装饰函数的签名和fn完全一致,这样类型检查器能确保参数传递是安全的。
- 当传入的
- 运行时的
inspect.signature判断逻辑和原来一致,但类型检查器会根据重载的定义,在不同分支下自动推断fn的类型,从而消除报错。
这样既保留了你想要的通用功能,又能让类型检查器完全满意,完美兼顾运行时正确性和类型安全性~
内容来源于stack exchange
相关产品推荐
相关产品推荐

