为返回子类的类装饰器添加类型提示的技术问题
类装饰器的类型注解修正方案
我有一批互不相关的类(包含导入类),它们都拥有dict[str, Any]类型的公共属性a。希望通过装饰器给这些类添加一个b属性,直接返回a.get("b", {}),简化调用逻辑(从inst.a.get("b", {})[some_key]变为inst.b[some_key])。以下是修正后的可通过mypy检查的实现:
from functools import wraps from typing import Any, Protocol, TypeVar, cast class AProtocol(Protocol): a: dict[str, Any] class BProtocol(AProtocol, Protocol): @property def b(self) -> dict[str, bool]: ... T = TypeVar("T", bound=AProtocol) def access_b(cls: type[T]) -> type[T]: @wraps(cls, updated=()) class Wrapper(cast(type, cls)): # 用cast告知mypy cls是合法基类 @property def b(self) -> dict[str, bool]: return self.a.get("b", {}) # 强制类型转换,让mypy认可Wrapper同时满足原类型和BProtocol契约 return cast(type[T], Wrapper) # 本地类使用装饰器 @access_b class Demo1: """Local class.""" def __init__(self, a: dict[str, Any]): self.a = a.copy() demo1 = Demo1({"b": {"allow_X": True}}) demo1.b["allow_X"] # mypy不再报错 # 导入类直接调用装饰器 class Demo2: """Imported class.""" def __init__(self, a: dict[str, Any]): self.a = a.copy() Demo2WithB = access_b(Demo2) demo2 = Demo2WithB({"b": {"allow_X": True}}) demo2.b["allow_X"] # mypy不再报错
疑问解答
1. 为什么cls不能作为有效基类?
mypy的静态类型检查要求类定义的基类是编译阶段可确定的静态类型(比如直接写类名),而cls是一个运行时变量,mypy无法在静态分析阶段确定它的具体类型,因此会抛出"Variable 'cls' is not valid as a type"错误。用cast(type, cls)强制类型转换,可跳过mypy的静态检查,告知其该变量是合法的类类型。
2. 如何标注返回“扩展后的原类型”?
- 定义绑定到
AProtocol的TypeVar,确保输入类都具备a属性; - 返回类型标注为
type[T],表示返回的类保留原类型的所有特性; - 通过
BProtocol描述扩展后的类契约(新增b属性),结合cast让mypy认可返回类同时满足原类型和扩展契约; - 无需单独定义两个TypeVar,单个TypeVar即可实现“原类型+扩展属性”的类型推断。
其他方案的问题说明
- 动态添加属性:mypy的静态分析不会追踪运行时新增的属性,因此无法识别动态添加的
b; - Mixin装饰器:本质上和子类工厂方案逻辑一致,同样需要借助
cast和TypeVar辅助mypy完成类型推断,否则会出现类似的类型检查错误。
内容的提问来源于stack exchange,提问作者ljmc
相关产品推荐
相关产品推荐

