如何让Mypy正确识别Python类中作为类变量的自身类实例?
问题场景
你定义了类A,需要将foo、bar两个A实例作为类变量通过A.foo、A.bar访问,但初始赋值为None的写法会导致Mypy将两个类变量识别为None类型,标注Optional[A]不符合实际预期。以下是三种可落地的解决方案:
解决方案
方案1:显式标注类变量+忽略初始赋值报错(最简便)
如果你可以保证类定义完成后一定会立刻为foo、bar赋值为A实例,不会出现访问时为None的情况,可以用ClassVar标注类变量类型,同时忽略初始赋值为None的类型报错:
from __future__ import annotations from typing import ClassVar class A: def __init__(self): pass foo: ClassVar[A] = None # type: ignore[assignment] bar: ClassVar[A] = None # type: ignore[assignment] A.foo = A() A.bar = A()
如果使用Python 3.11及以上版本,可以用Self简化前向引用写法:
from typing import ClassVar, Self class A: def __init__(self): pass foo: ClassVar[Self] = None # type: ignore[assignment] bar: ClassVar[Self] = None # type: ignore[assignment] A.foo = A() A.bar = A()
该方案下Mypy会直接将两个类变量识别为A类型,不会携带Optional标记。
方案2:用typing.cast强制类型转换(无忽略注释)
如果你不想使用type: ignore注释,可以先声明类变量类型不做初始赋值,赋值时通过cast明确告知Mypy类型:
from __future__ import annotations from typing import ClassVar, cast class A: def __init__(self): pass foo: ClassVar[A] bar: ClassVar[A] cast(type[A], A).foo = A() cast(type[A], A).bar = A()
方案3:元类自动完成赋值(从根源避免None中间状态)
如果希望彻底消除类变量为None的可能性,可以通过元类在类创建完成后自动完成实例赋值:
from __future__ import annotations from typing import ClassVar class AutoClassVarMeta(type): def __new__(cls, name, bases, attrs): new_cls = super().__new__(cls, name, bases, attrs) new_cls.foo = new_cls() new_cls.bar = new_cls() return new_cls class A(metaclass=AutoClassVarMeta): def __init__(self): pass foo: ClassVar[A] bar: ClassVar[A]
该方案下类加载完成后A.foo、A.bar就已经是A实例,不存在为None的中间状态,Mypy可以完全正确识别类型,不需要任何额外的忽略注释。
内容的提问来源于stack exchange,提问作者Knaapje
相关产品推荐
相关产品推荐

