我正在尝试创建一个带有类变量的类,该变量代表该类的空实例。我现在拥有的是
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
。
如果我在类中取消注释
EMPTY
的定义,我会得到 NameError
,因为我在定义 Thing
时尝试引用它。
如果我尝试在类中将
EMPTY
声明为 EMPTY = None
并将其分配到类外,Mypy 会不高兴,因为它认为 EMPTY
的类型是 None
。
如果我尝试使用
EMPTY
作为类型来注释 Optional[Thing]
,那么我会在定义之前重新使用 Thing
。
有没有解决方案,或者我只需要告诉 Mypy 忽略
EMPTY
字段?
我使用的是python 3.9。
您可以注释变量而不为其赋值。这在这里很有用,因为您不能使用类型的名称
Thing
在其自己的类主体中创建实例,因为全局名称 Thing
直到创建类对象之后才定义。所以你只需要一个注释,其值稍后定义。
缺少全局名称 Thing
也是您迄今为止对属性进行注释的尝试不起作用的原因。解决方案是使用带引号的字符串进行前向引用。在定义
"Thing"
之前,您可以使用
Thing
来注释类实例所在的位置。(Python 3.11 将于 2022 年秋季发布,将包括
PEP 673,它将提供一种更好的方式从类的主体或方法中引用“当前类”:typing.Self
。这将是完美的用于解决我们的前向参考问题,但尚未解决。)在类体中注释属性通常会告诉类型检查器该属性将是一个实例变量(这是类型检查器所做的默认假设)。如果您希望该属性成为类变量,则需要在注释中使用
typing.ClassVar
来表明这一点。所以,把这些放在一起,你可能想要这样的东西:
import typing
class Thing(_Thing):
EMPTY: typing.ClassVar["Thing"]
#...
Thing.EMPTY = Thing(None, None)
如果您将此代码升级到 Python 3.11,您可以将注释中的 "Thing"
替换为
typing.Self
。
请注意,如果您使用
from __future__ import annotations
你可以这样做:
... class Thing(_Thing): EMPTY: typing.ClassVar[Thing] ...
from typing import ClassVar
from typing import Self
class Thing:
EMPTY_THING: ClassVar[Self]
@classmethod
def __defaults__(cls) -> None:
cls.EMPTY_THING = cls()
Thing.__defaults__()
if __name__ == '__main__':
assert Thing.EMPTY_THING is not None