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

Python初始化后只读实例属性描述器的类型标注问题

解决只读属性描述器的Mypy类型检查问题

问题背景

需要为多个数据结构类实现公共只读属性,由于子类需要自定义__init__的args和kwargs,无法使用dataclasses或namedtuples。使用@property逐个实现属性在数量超过20个时过于繁琐,因此选择实现描述器方案,但遇到Mypy类型检查错误:

  • 给类属性标注目标类型(如str)时,Mypy会报错“类型不兼容”,因为实际赋值的是ReadOnlyAttribute实例
  • 不标注类型时,泛型参数会被推断为Never,导致reveal_type结果不符合预期

解决方案

核心是为描述器实例指定泛型参数,并正确区分类属性与实例属性的类型:

修正后的代码

from typing import Type, TypeVar, Generic, ClassVar

_Object = TypeVar("_Object")
_Value = TypeVar("_Value")


class ReadOnlyAttribute(Generic[_Value]):
    def __set_name__(self, owner: Type[_Object], name: str) -> None:
        self.public_name = name
        self.private_name = f"_{name}"

    def __get__(self, instance: _Object, owner: Type[_Object]) -> _Value:
        return getattr(instance, self.private_name)

    def __set__(self, instance: _Object, value: _Value) -> None:
        if hasattr(instance, self.private_name):
            raise AttributeError(
                f"Cannot set read-only attribute '{self.public_name}' of '{instance}'"
            )
        setattr(instance, self.private_name, value)


class Test:
    # 用ClassVar标注类属性类型,同时指定描述器的泛型参数为str
    foo: ClassVar[ReadOnlyAttribute[str]] = ReadOnlyAttribute[str]()

    def __init__(self):
        self.foo = "foooooo"


# Revealed type is "builtins.str",符合预期
reveal_type(Test().foo)

简化写法(无需ClassVar)

如果不需要显式标注类属性的类型,直接实例化带泛型参数的描述器即可,Mypy会自动推断实例属性的类型:

class Test:
    foo = ReadOnlyAttribute[str]()

    def __init__(self):
        self.foo = "foooooo"

reveal_type(Test().foo)  # 同样会显示为builtins.str

原理说明

  1. 指定泛型参数:ReadOnlyAttribute[str]()明确告诉Mypy该描述器管理的是str类型的属性,解决了泛型被推断为Never的问题
  2. ClassVar标注:ClassVar[ReadOnlyAttribute[str]]明确类属性foo的类型是描述器实例,而非实例属性的str类型,消除了Mypy的类型不兼容错误
  3. Mypy会通过描述器的__get__方法返回值类型,正确推断出实例属性Test().foo的类型为str

内容的提问来源于stack exchange,提问作者Parikshit

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:52:39