如何处理自定义 PyYAML 构造函数中的递归



PyYAML可以处理常规python对象中的循环图。例如:

代码段 #1。

class Node: pass
a = Node()
b = Node()
a.child = b
b.child = a
# We now have the cycle a->b->a
serialized_object  = yaml.dump(a)
object = yaml.load(serialized_object)

此代码成功,因此很明显,在加载序列化对象时有一些机制可以防止无限递归。当我编写自己的 YAML 构造函数时,如何利用它?

例如,假设Node是一个具有瞬态场foobar以及非瞬态场child的类。只有child才能将其放入 yaml 文档中。我希望这样做:

片段 #2。

def representer(dumper, node):
  return dumper.represent_mapping("!node", {"child": node.child})
def constructor(loader, data):
  result = Node()
  mapping = loader.construct_mapping(data)
  result.child = mapping["child"]
  return result
yaml.add_representer(Node, representer)
yaml.add_constructor("!node", constructor)
# Retry object cycle a->b->a from earlier code snippet
serialized_object  = yaml.dump(a)
print serialized_object
object = yaml.load(serialized_object)

但它失败了:

&id001 !node
child: !node
  child: *id001
yaml.constructor.ConstructorError: found unconstructable recursive node:
  in "<string>", line 1, column 1:
    &id001 !node

我明白为什么了。我的构造函数不是为递归而构建的。它需要在完成父对象的构造之前返回子对象,当子对象和父对象是同一对象时,这将失败。

但很明显,PyYAML有图遍历来解决这个问题,因为代码片段#1有效。也许有一个通道来构造所有对象,第二个过程来填充它们的字段。我的问题是,我的自定义构造函数如何与这些机制相关联?

对这个问题的回答将是理想的。但是,如果答案是我无法使用自定义构造函数执行此操作,并且有一个不太理想的替代方案(例如,将YAMLObject类混合到我的Node类中),那么该答案也将不胜感激。

对于可能涉及递归(映射/字典、序列/列表、对象)的复杂类型,构造函数无法一次性创建对象。因此,您应该在constructor()函数中yield构造的对象,然后更新之后的任何值¹:

def constructor(loader, data):
    result = Node()
    yield result
    mapping = loader.construct_mapping(data)
    result.child = mapping["child"]

这样可以消除错误。

¹ 我认为这在任何地方都没有记录,如果没有我仔细研究py/constructor.py,在将 PyYAML 升级到 ruamel.yaml 时,我不会知道如何做到这一点。一个典型案例:阅读源路加

我对 PyYaml 的第一印象是它试图将某种程度的一致接口/行为维护为 JSON(转储/加载)。

我学习并欣赏了 JSON 功能,因为我很容易将 JSON 读入动态构造的类型。然而,我对JSON格式本身有问题,特别是缺乏对多行字符串,注释和可读性的支持。

使用 PyYAML,我发现将 yaml 反序列化为一种类型非常困难。似乎有很多箍要跳过,我没有时间/兴趣去学习。请考虑以下将 JSON 反序列化为类型的代码:

with open(file) as filereader: json.load(filereader, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))

通过使用对象加载钩子,我可以将字典转换为命名元组。现在pyyaml非常擅长将yaml转换为字典。我最终应用了这个技巧,我从 yamlfile -> 字典 -> json 字符串 -> 对象流出,如下所示:

json.loads(json.dumps(yaml.load(filereader)), object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))

这一行通过中间 json 翻译将 yaml 文件读入类型化对象。就我而言,这是一个值得的黑客攻击,因为替代方案要复杂得多。

相关内容

  • 没有找到相关文章

最新更新