python - 是否可以在 python 中使用 make_dataclass 创建递归数据类?
问题描述
这是一个简单的例子,我试图创建一个递归节点定义,其中包含一个可选的子节点,它也是一个节点。代码编译但是当我尝试访问我得到的类型定义时node
没有定义。有可能绕过这个错误吗?
import dataclasses
import typing as t
node_type = dataclasses.make_dataclass(
"node", [("child", t.Optional["node"], dataclasses.field(default=None))]
)
print(t.get_type_hints(node_type))
输出
NameError: name 'node' is not defined
我正在使用 python 3.9.2。
解决方案
这里存在三个问题。它们是可解的,但在您实际使用dataclasses.make_dataclass
.
第一个问题是,typing.get_type_hints
正在寻找一个名为的类'node'
,而你却调用了全局变量node_type
。您传递给make_dataclass
的名称、您在注释中使用的名称以及您分配给数据类的名称都必须相同:
Node = dataclasses.make_dataclass(
"Node", [("child", t.Optional["Node"], dataclasses.field(default=None))]
)
但这仍然不够,因为typing.get_type_hints
没有在正确的命名空间中查找。这是第二个问题。
当您调用typing.get_type_hints
一个类时,typing.get_type_hints
将尝试通过查看定义该类的模块来解析字符串注释。它通过查看__module__
类中的条目来确定该模块__dict__
。因为您以一种不通过正常class
语句的奇怪方式创建了节点类,所以该类__module__
没有设置为引用正确的模块。相反,它设置为'types'
.
您可以通过手动预设当前模块__module__
来解决此问题:__name__
Node = dataclasses.make_dataclass(
"Node",
[("child", t.Optional["Node"], dataclasses.field(default=None))],
namespace={'__module__': __name__}
)
然后typing.get_type_hints
就能解析出字符串注解。
元问题是,如果您dataclasses.make_dataclass
在实践中使用,您可能不知道类名。您可能在函数中和/或循环中使用它。typing.get_type_hints
必须能够通过匹配类名的全局变量找到类,但是动态变量名比较乱。
您可以采取简单的方法,即使用以下方法设置全局globals()
:
globals()[your_dataclass.__name__] = your_dataclass
但这很危险。如果两个生成的类具有相同的名称,则第二个将替换第一个。如果生成的类与全局命名空间中的其他名称具有相同的名称,例如,如果您from some_dependency import Thing
生成了一个名为 的类Thing
,则生成的类将覆盖现有的全局值。
如果你能保证这些事情不会发生,globals()
也许没问题。如果你不能做出这样的保证,你可能需要为每个生成的类生成一个新模块,这样它们每个都有自己独立的全局命名空间,或者你可能只是接受并记录这样的get_type_hints
事实t 适用于您生成的类。