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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在自定义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有图形遍历可以解决这个问题,因为Snippet#1可以工作。也许有一个传递来构造所有对象,第二个传递来填充它们的字段。我的问题是,我的自定义构造函数如何与这些机制联系起来?

这个问题的答案是理想的。但如果答案是我不能用自定义构造函数做这个,并且有一个不太理想的替代方案(例如将YAMLObject类混合到我的Node类中),那么该答案也将受到赞赏。

答案

对于可能涉及递归(mapping / dict,sequence / list,objects)的复杂类型,构造函数无法一次创建对象。因此,您应该在yield函数中constructor()构造的对象,然后更新之后的任何值¹:

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()))

通过使用对象加载钩子我可以将字典转换为namedtuple。现在pyyaml非常善于将yaml转换为字典。我最终应用了这个hack,我从yamlfile流出 - >字典 - > json string - >对象如下:

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

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

以上是关于如何在自定义PyYAML构造函数中处理递归?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 PyYAML 使用生成器来构造对象?

pyyaml 中的默认构造函数参数

如何在自定义小部件构造函数中添加和使用键

在自定义视图中调用 super() 时,确保传递与组件的构造函数相同的 props

实用的 PyYAML 使用技巧

mPDF 不使用构造函数参数