为什么 PyYAML 使用生成器来构造对象?
Why does PyYAML use generators to construct objects?
我一直在阅读 PyYAML 源代码,试图了解如何定义我可以使用 add_constructor
添加的适当构造函数。我现在非常了解该代码的工作原理,但我仍然不明白为什么 SafeConstructor
中的默认 YAML 构造函数是生成器。比如SafeConstructor
的方法construct_yaml_map
:
def construct_yaml_map(self, node):
data = {}
yield data
value = self.construct_mapping(node)
data.update(value)
我了解如何在 BaseConstructor.construct_object
中使用生成器,如下所示,如果 deep=False
传递给 construct_mapping
,则只用来自节点的数据填充对象:
if isinstance(data, types.GeneratorType):
generator = data
data = generator.next()
if self.deep_construct:
for dummy in generator:
pass
else:
self.state_generators.append(generator)
并且我了解在 deep=False
对应 construct_mapping
的情况下如何在 BaseConstructor.construct_document
中生成数据。
def construct_document(self, node):
data = self.construct_object(node)
while self.state_generators:
state_generators = self.state_generators
self.state_generators = []
for generator in state_generators:
for dummy in generator:
pass
我不明白的是 删除数据对象并通过迭代 construct_document
中的生成器来处理对象的 好处。是否必须这样做以支持 YAML 规范中的某些内容,或者它是否提供了性能优势?
This answer on another question 有点帮助,但我不明白为什么这个答案会这样:
def foo_constructor(loader, node):
instance = Foo.__new__(Foo)
yield instance
state = loader.construct_mapping(node, deep=True)
instance.__init__(**state)
而不是这个:
def foo_constructor(loader, node):
state = loader.construct_mapping(node, deep=True)
return Foo(**state)
我已经测试过后一种形式适用于其他答案中发布的示例,但也许我遗漏了一些边缘情况。
我使用的是 3.10 版的 PyYAML,但看起来问题代码在最新版本 (3.12) 的 PyYAML 中是相同的。
在 YAML 中你可以有 anchors and aliases。有了它,您可以直接或间接地制作 self-referential 结构。
如果YAML没有这种self-reference的可能性,你可以先构造所有的children,然后一次性创建parent结构。但是由于 self-reference,您可能还没有 child 到 "fill-out" 您正在创建的结构。通过使用生成器的 two-step 过程(我称之为两步,因为在方法结束之前它只有一个产量),您可以创建一个 object 部分并填充它用 self-reference 输出,因为 object 存在(即定义了它在内存中的位置)。
好处不在于速度,而纯粹是因为使 self-reference 成为可能。
如果根据您参考的答案稍微简化示例,将加载以下内容:
import sys
import ruamel.yaml as yaml
class Foo(object):
def __init__(self, s, l=None, d=None):
self.s = s
self.l1, self.l2 = l
self.d = d
def foo_constructor(loader, node):
instance = Foo.__new__(Foo)
yield instance
state = loader.construct_mapping(node, deep=True)
instance.__init__(**state)
yaml.add_constructor(u'!Foo', foo_constructor)
x = yaml.load('''
&fooref
!Foo
s: *fooref
l: [1, 2]
d: {try: this}
''', Loader=yaml.Loader)
yaml.dump(x, sys.stdout)
但如果您将 foo_constructor()
更改为:
def foo_constructor(loader, node):
instance = Foo.__new__(Foo)
state = loader.construct_mapping(node, deep=True)
instance.__init__(**state)
return instance
(yield 已删除,添加了最后一个 return),你会得到一个 ConstructorError
: with as message
found unconstructable recursive node
in "<unicode string>", line 2, column 1:
&fooref
PyYAML 应该给出类似的信息。检查该错误的回溯,您可以看到 ruamel.yaml/PyYAML 尝试解析源代码中的别名的位置。
我一直在阅读 PyYAML 源代码,试图了解如何定义我可以使用 add_constructor
添加的适当构造函数。我现在非常了解该代码的工作原理,但我仍然不明白为什么 SafeConstructor
中的默认 YAML 构造函数是生成器。比如SafeConstructor
的方法construct_yaml_map
:
def construct_yaml_map(self, node):
data = {}
yield data
value = self.construct_mapping(node)
data.update(value)
我了解如何在 BaseConstructor.construct_object
中使用生成器,如下所示,如果 deep=False
传递给 construct_mapping
,则只用来自节点的数据填充对象:
if isinstance(data, types.GeneratorType):
generator = data
data = generator.next()
if self.deep_construct:
for dummy in generator:
pass
else:
self.state_generators.append(generator)
并且我了解在 deep=False
对应 construct_mapping
的情况下如何在 BaseConstructor.construct_document
中生成数据。
def construct_document(self, node):
data = self.construct_object(node)
while self.state_generators:
state_generators = self.state_generators
self.state_generators = []
for generator in state_generators:
for dummy in generator:
pass
我不明白的是 删除数据对象并通过迭代 construct_document
中的生成器来处理对象的 好处。是否必须这样做以支持 YAML 规范中的某些内容,或者它是否提供了性能优势?
This answer on another question 有点帮助,但我不明白为什么这个答案会这样:
def foo_constructor(loader, node):
instance = Foo.__new__(Foo)
yield instance
state = loader.construct_mapping(node, deep=True)
instance.__init__(**state)
而不是这个:
def foo_constructor(loader, node):
state = loader.construct_mapping(node, deep=True)
return Foo(**state)
我已经测试过后一种形式适用于其他答案中发布的示例,但也许我遗漏了一些边缘情况。
我使用的是 3.10 版的 PyYAML,但看起来问题代码在最新版本 (3.12) 的 PyYAML 中是相同的。
在 YAML 中你可以有 anchors and aliases。有了它,您可以直接或间接地制作 self-referential 结构。
如果YAML没有这种self-reference的可能性,你可以先构造所有的children,然后一次性创建parent结构。但是由于 self-reference,您可能还没有 child 到 "fill-out" 您正在创建的结构。通过使用生成器的 two-step 过程(我称之为两步,因为在方法结束之前它只有一个产量),您可以创建一个 object 部分并填充它用 self-reference 输出,因为 object 存在(即定义了它在内存中的位置)。
好处不在于速度,而纯粹是因为使 self-reference 成为可能。
如果根据您参考的答案稍微简化示例,将加载以下内容:
import sys
import ruamel.yaml as yaml
class Foo(object):
def __init__(self, s, l=None, d=None):
self.s = s
self.l1, self.l2 = l
self.d = d
def foo_constructor(loader, node):
instance = Foo.__new__(Foo)
yield instance
state = loader.construct_mapping(node, deep=True)
instance.__init__(**state)
yaml.add_constructor(u'!Foo', foo_constructor)
x = yaml.load('''
&fooref
!Foo
s: *fooref
l: [1, 2]
d: {try: this}
''', Loader=yaml.Loader)
yaml.dump(x, sys.stdout)
但如果您将 foo_constructor()
更改为:
def foo_constructor(loader, node):
instance = Foo.__new__(Foo)
state = loader.construct_mapping(node, deep=True)
instance.__init__(**state)
return instance
(yield 已删除,添加了最后一个 return),你会得到一个 ConstructorError
: with as message
found unconstructable recursive node
in "<unicode string>", line 2, column 1:
&fooref
PyYAML 应该给出类似的信息。检查该错误的回溯,您可以看到 ruamel.yaml/PyYAML 尝试解析源代码中的别名的位置。