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
原理说明
- 指定泛型参数:
ReadOnlyAttribute[str]()明确告诉Mypy该描述器管理的是str类型的属性,解决了泛型被推断为Never的问题 - ClassVar标注:
ClassVar[ReadOnlyAttribute[str]]明确类属性foo的类型是描述器实例,而非实例属性的str类型,消除了Mypy的类型不兼容错误 - Mypy会通过描述器的
__get__方法返回值类型,正确推断出实例属性Test().foo的类型为str
内容的提问来源于stack exchange,提问作者Parikshit
相关产品推荐
相关产品推荐

