12

我一直在阅读 PyYAML 源代码,试图了解如何定义一个可以添加的正确构造函数add_constructor。我现在对该代码的工作原理有了很好的理解,但我仍然不明白为什么其中的默认 YAML 构造函数SafeConstructor是生成器。例如,方法construct_yaml_mapSafeConstructor

def construct_yaml_map(self, node):
    data = {}
    yield data
    value = self.construct_mapping(node)
    data.update(value)

我了解生成器如何用于以下方式来存根一个对象,并且如果传递给BaseConstructor.construct_object它,则仅使用来自节点的数据填充它:deep=Falseconstruct_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)

而且我了解在forBaseConstructor.construct_document的情况下如何生成数据。deep=Falseconstruct_mapping

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,但看起来有问题的代码在 PyYAML 的最新版本(3.12)中是相同的。

4

1 回答 1

10

在 YAML 中,您可以拥有锚点和别名。有了它,您可以直接或间接地创建自引用结构。

如果 YAML 没有这种自引用的可能性,您可以先构造所有子级,然后一次性创建父级结构。但是由于自我引用,您可能还没有孩子来“填写”您正在创建的结构。通过使用生成器的两步过程(我称之为两步,因为在方法结束之前它只有一个yield),您可以部分创建一个对象并使用自引用填充它,因为对象存在(即它在内存中的位置已定义)。

好处不在于速度,而纯粹是因为使自引用成为可能。

如果您从您引用的答案中简化示例,则会加载以下内容:

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

(删除了产量,添加了最终回报),你会得到一个ConstructorError: 和 as 消息

found unconstructable recursive node 
  in "<unicode string>", line 2, column 1:
    &fooref

PyYAML 应该给出类似的信息。检查该错误的回溯,您可以看到 ruamel.yaml/PyYAML 尝试在源代码中解析别名的位置。

于 2017-01-27T18:48:11.813 回答