二阶装饰器的Mypy类型标注错误排查与修正咨询
二阶装饰器类型标注问题
我尝试实现一个二阶装饰器,将动态变量作为被装饰函数的首个参数注入,现有代码如下:
from typing import Callable, Concatenate, ParamSpec, TypeVar T = TypeVar('T') U = TypeVar('U') P = ParamSpec('P') def foo(some_arg: U) -> Callable[ [Callable[Concatenate[U, P], T]], Callable[P, T], ]: def decorator(fn: Callable[Concatenate[U, P], T]) -> Callable[P, T]: def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: return fn(some_arg, *args, **kwargs) return wrapper return decorator
运行mypy时出现如下错误:
error: Incompatible return value type ( got "Callable[[Arg(Callable[[U, **P], T], 'fn')], Callable[P, T]]", expected "Callable[[Callable[[U, **P], T]], Callable[P, T]]") [return-value]
我搞不懂错误原因,也找不到Arg相关的文档,想请教:
- 如何正确声明装饰器工厂的返回值?
- 怎么解读这个mypy错误?
- 有没有遗漏的官方文档内容?
错误解读
这个错误里的Arg(...)是mypy内部用来标记带参数名的类型结构的语法,不属于标准Python类型体系。错误的核心原因是:你在foo函数的返回类型注解中,使用了未绑定的泛型变量P和T——这两个泛型变量的作用域应该绑定到内部的decorator函数,而非外层的foo。mypy因此认为返回的decorator函数类型,和你声明的返回类型不匹配。
修复方案
要正确标注,需要将泛型参数P和T的绑定移到decorator函数上(Python 3.12+支持函数级泛型),或者直接将泛型参数声明在foo函数上。
方案1:函数级泛型(Python 3.12+)
from typing import Callable, Concatenate, ParamSpec, TypeVar T = TypeVar('T') U = TypeVar('U') P = ParamSpec('P') def foo(some_arg: U) -> Callable[[Callable[Concatenate[U, P], T]], Callable[P, T]]: def decorator[P, T](fn: Callable[Concatenate[U, P], T]) -> Callable[P, T]: def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: return fn(some_arg, *args, **kwargs) return wrapper return decorator
方案2:外层函数声明泛型
from typing import Callable, Concatenate, ParamSpec, TypeVar T = TypeVar('T') U = TypeVar('U') P = ParamSpec('P') def foo[P, T, U](some_arg: U) -> Callable[[Callable[Concatenate[U, P], T]], Callable[P, T]]: def decorator(fn: Callable[Concatenate[U, P], T]) -> Callable[P, T]: def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: return fn(some_arg, *args, **kwargs) return wrapper return decorator
两种写法都能让泛型变量P、T与被装饰的函数正确绑定,解决类型不匹配的问题。
关于Arg的说明
Arg是mypy内部用于追踪参数名称和类型对应关系的结构,不会出现在标准Python类型提示中。它只是mypy在报错时,更详细地展示了参数的名称信息,你不需要在代码中使用它。
文档参考
mypy官方文档中,泛型函数和装饰器类型标注的章节,对Concatenate与ParamSpec的结合使用有详细说明,可重点关注这部分内容。
内容的提问来源于stack exchange,提问作者Nicolas Appriou
相关产品推荐
相关产品推荐

