如何为要求首参为Iterable的Python装饰器添加正确类型注解?
解决装饰器类型注解的逆变问题
错误原因分析
你遇到的问题确实是Callable参数的逆变特性导致的:Callable的参数类型是逆变的——如果装饰器期望接收一个参数类型为Iterable[int]的函数,只有参数类型比Iterable[int]更宽泛的函数(比如接受object)才能适配;而list[int]是Iterable[int]的子类型(更具体),所以无法满足原装饰器的类型要求。
正确的类型注解写法
我们需要通过泛型约束让装饰器接受任意Iterable的子类型作为第一个参数,而非固定死Iterable[int]。修改后的代码如下:
from typing import Callable, Concatenate, Iterable, ParamSpec, TypeVar # 定义受约束的类型变量,代表任意Iterable的子类型 T = TypeVar("T", bound=Iterable[object]) P = ParamSpec("P") R = TypeVar("R") def printcontent(f: Callable[Concatenate[T, P], R]) -> Callable[Concatenate[T, P], R]: def func(xs: T, *args: P.args, **kwargs: P.kwargs) -> R: for x in xs: print(x) return f(xs, *args, **kwargs) return func @printcontent def mysum(xs: list[int]) -> int: return sum(xs) # 测试其他Iterable类型也能正常工作 @printcontent def count_chars(s: str) -> int: return len(s)
说明
T = TypeVar("T", bound=Iterable[object]):约束T必须是Iterable的子类型,确保装饰器可以接受任何实现迭代协议的类型(list、str、tuple等)作为第一个参数。- 装饰函数时,泛型参数
T会自动推断为具体类型(比如list[int]或str),既适配被装饰函数的参数类型,又满足装饰器遍历第一个参数的逻辑。
这样修改后,Mypy就能正常通过类型检查,同时保留装饰器的原有功能。
内容的提问来源于stack exchange,提问作者hotoku
相关产品推荐
相关产品推荐

