You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为返回子类的类装饰器添加类型提示的技术问题

类装饰器的类型注解修正方案

我有一批互不相关的类(包含导入类),它们都拥有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 04:40:17