是否可以使用 python 中的 make_dataclass 创建递归数据类?
Is it possible to create a recursive dataclass using make_dataclass in python?
这是一个简单的例子,我试图创建一个递归节点定义,其中包含一个可选的子节点,它也是一个节点。代码可以编译,但是当我尝试访问类型定义时,我得到 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'
的class,但是你调用了全局变量node_type
。您传递给 make_dataclass
的名称、您在注释中使用的名称以及您分配数据 class 的名称都必须相同:
Node = dataclasses.make_dataclass(
"Node", [("child", t.Optional["Node"], dataclasses.field(default=None))]
)
但这还不够,因为 typing.get_type_hints
没有在正确的命名空间中查找。这是第二个问题。
当您在 class 上调用 typing.get_type_hints
时,typing.get_type_hints
将尝试通过查看定义 class 的模块来解析字符串注释。它通过查看 class 的 __dict__
中的 __module__
条目来确定该模块。因为您以不通过正常 class
语句的奇怪方式创建了节点 class,所以 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
,您可能不知道 class 名称。您可能在函数中使用它,and/or 在循环中。 typing.get_type_hints
必须能够通过匹配 class 名称的全局变量找到 class,但是动态变量名称很乱。
您可以采用简单的方法,只需使用 globals()
:
设置全局变量
globals()[your_dataclass.__name__] = your_dataclass
但这很危险。如果生成的两个 classes 具有相同的名称,则第二个将替换第一个。如果生成的 class 与全局命名空间中的其他名称同名,例如如果您执行了 from some_dependency import Thing
然后生成了名为 Thing
的 class,则生成的 class 将踩踏现有的全局值。
如果你能保证那些事情不会发生,globals()
可能没问题。如果你不能做出这样的保证,你可能需要做一些事情,比如为每个生成的 class 生成一个新模块来居住,这样他们每个人都有自己独立的全局命名空间,或者你可能只是接受并记录事实上 get_type_hints
不适用于您生成的 classes.
这是一个简单的例子,我试图创建一个递归节点定义,其中包含一个可选的子节点,它也是一个节点。代码可以编译,但是当我尝试访问类型定义时,我得到 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'
的class,但是你调用了全局变量node_type
。您传递给 make_dataclass
的名称、您在注释中使用的名称以及您分配数据 class 的名称都必须相同:
Node = dataclasses.make_dataclass(
"Node", [("child", t.Optional["Node"], dataclasses.field(default=None))]
)
但这还不够,因为 typing.get_type_hints
没有在正确的命名空间中查找。这是第二个问题。
当您在 class 上调用 typing.get_type_hints
时,typing.get_type_hints
将尝试通过查看定义 class 的模块来解析字符串注释。它通过查看 class 的 __dict__
中的 __module__
条目来确定该模块。因为您以不通过正常 class
语句的奇怪方式创建了节点 class,所以 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
,您可能不知道 class 名称。您可能在函数中使用它,and/or 在循环中。 typing.get_type_hints
必须能够通过匹配 class 名称的全局变量找到 class,但是动态变量名称很乱。
您可以采用简单的方法,只需使用 globals()
:
globals()[your_dataclass.__name__] = your_dataclass
但这很危险。如果生成的两个 classes 具有相同的名称,则第二个将替换第一个。如果生成的 class 与全局命名空间中的其他名称同名,例如如果您执行了 from some_dependency import Thing
然后生成了名为 Thing
的 class,则生成的 class 将踩踏现有的全局值。
如果你能保证那些事情不会发生,globals()
可能没问题。如果你不能做出这样的保证,你可能需要做一些事情,比如为每个生成的 class 生成一个新模块来居住,这样他们每个人都有自己独立的全局命名空间,或者你可能只是接受并记录事实上 get_type_hints
不适用于您生成的 classes.