Python中如何用Mypy正确标注staticmethod+abstractmethod+property类型
问题复现
给定测试代码如下:
from abc import ABC, abstractmethod from typing import TYPE_CHECKING class Foo(ABC): @property @staticmethod @abstractmethod def name() -> str: pass class Bar(Foo): name = "bar" class Baz(Foo): name = "baz" instances = [Bar(), Baz()] print(instances[0].name) if TYPE_CHECKING: reveal_type(instances[0].name)
- 直接用Python解释器运行,输出符合预期:
bar
- 用mypy做类型检查时,得到不符合预期的推断结果:
main.py:23: note: Revealed type is "def () -> builtins.str"
期望推断结果为str类型,实际被错误推断为无参、返回值为str的函数类型。需求为尽量在Foo基类层面完成一次性修复,适配项目中多处访问.name属性的场景。
问题原因
核心错误是装饰器的堆叠组合不符合Python语义:
Python装饰器按照从下到上的顺序执行,原代码中@property、@staticmethod、@abstractmethod的堆叠写法,实际等价于name = property(staticmethod(abstractmethod(name)))。@property本身是实现了描述符协议的装饰器,要求传入的是绑定到实例的方法,和@staticmethod组合本身就是非法用法——该组合在Python 3.10前会直接抛出运行时错误,高版本Python下仅因为子类直接覆盖了同名类属性才没有触发运行时报错,mypy这类静态检查工具无法识别这种错误写法的语义,才会出现类型推断错误。
修复方案
直接修改Foo基类的定义即可,子类代码无需任何改动,即可同时满足运行时行为不变、mypy类型推断正确、抽象约束生效三个要求。
推荐写法如下:
from abc import ABC, abstractmethod from typing import TYPE_CHECKING class Foo(ABC): @property @abstractmethod def name(self) -> str: pass
修改后验证:
- 运行代码输出依然为
bar,和原行为完全一致 - mypy检查时
reveal_type(instances[0].name)会正确输出Revealed type is "builtins.str" - 如果子类未定义
name属性,mypy和Python运行时都会正常抛出抽象类未实现对应方法/属性的错误,保留原有的抽象约束能力。
如果你的name属性明确是所有实例共享的类属性,也可以用ClassVar做更精准的标注,效果完全一致:
from abc import ABC, abstractmethod from typing import ClassVar, TYPE_CHECKING class Foo(ABC): name: ClassVar[str] = abstractmethod(lambda: "")
注:不要尝试保留
@staticmethod和@property的堆叠写法,该写法本身就是Python不推荐的反模式,没有任何静态检查工具会适配这种错误用法。
内容的提问来源于stack exchange,提问作者Delgan
相关产品推荐
相关产品推荐

