带有 class 变量的 Mypy,它是 class 的一个实例

Mypy with class variable that is an instance of the class

我正在尝试使用 class 和代表 class 的空实例的 class 变量。我目前拥有的是

from collections import namedtuple
# from typing import Optional

_Thing = namedtuple("_Thing", ["foo", "bar"])

class Thing(_Thing):
    __slots__ = ()

    def baz(self):
        print("foo", self.foo)

    # NameError: name 'Thing' is not defined
    # EMPTY = Thing(None, None)

Thing.EMPTY = Thing(None, None)

if __name__ == '__main__':
    thing = Thing.EMPTY

    thing.baz()

    print("Done")

我也在尝试 运行 Mypy 上的代码。当我 运行 python simple.py 时,它 运行 如预期的那样:

$ python simple.py && mypy simple.py 
foo None
Done
simple.py:15: error: "Type[Thing]" has no attribute "EMPTY"
simple.py:18: error: "Type[Thing]" has no attribute "EMPTY"
Found 2 errors in 1 file (checked 1 source file)

但是 Mypy 不高兴,因为 Thing 的声明没有定义 EMPTY.

如果我在 class 中取消注释 EMPTY 的定义,我会得到一个 NameError,因为我在定义时试图引用 Thing

如果我尝试将 class 中的 EMPTY 声明为 EMPTY = None 并将其分配到 class 之外,Mypy 会不高兴,因为它认为 [= 的类型16=] 是 None.

如果我尝试使用 Optional[Thing] 作为类型注释 EMPTY,那么我会在定义之前返回使用 Thing

是否有解决方案,或者我只需要告诉 Mypy 忽略 EMPTY 字段?

我正在使用 python 3.9.

您可以在不为变量赋值的情况下对其进行注释。这在这里很有用,因为您不能使用类型 Thing 的名称在它自己的 class 体内创建实例,因为全局名称 Thing 直到 之后创建了class对象。所以你只需要一个注解,其值稍后定义。

缺少全局名称 Thing 也是您迄今为止对属性进行注释的尝试没有奏效的原因。解决方案是使用带引号的字符串进行前向引用。在定义 Thing 之前,您可以使用 "Thing" 来注释 class 的实例所在的位置。

(Python 3.11,将于 2022 年秋季推出,将包括 PEP 673,这将提供一种更好的方式来从class 的主体或方法:typing.Self。它非常适合解决我们的前向引用问题,但还没有出来。)

在 class 主体中注释属性通常会告诉类型检查器该属性将是一个实例变量(这是类型检查器的默认假设)。如果您希望属性成为 class 变量,则需要在注释中使用 typing.ClassVar 来表明这一点。

因此,将所有这些放在一起,您可能想要这样的东西:

import typing

class Thing(_Thing):
    EMPTY: typing.ClassVar["Thing"]
    #...

Thing.EMPTY = Thing(None, None)

如果您曾经将此代码升级到 Python 3.11,则可以将注释中的 "Thing" 替换为 typing.Self