【问题标题】:How do I handle recursion in a custom PyYAML constructor?如何在自定义 PyYAML 构造函数中处理递归?
【发布时间】:2015-03-05 18:25:45
【问题描述】:

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 类中),那么这个答案也将不胜感激。

【问题讨论】:

    标签: python constructor pyyaml graph-traversal cyclic-dependency


    【解决方案1】:

    对于可能涉及递归的复杂类型(映射/字典、序列/列表、对象),构造函数无法一次性创建对象。因此,您应该在 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 时,我不知道该怎么做。典型案例:阅读源卢克

    【讨论】:

    • 这很好用。这种情况下的典型建议现在可以升级为 read stackoverflow Luke,这确实是更受欢迎的建议。感谢您的修复!
    【解决方案2】:

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

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

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

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

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-27
      • 1970-01-01
      • 1970-01-01
      • 2018-06-06
      • 2011-11-05
      • 1970-01-01
      相关资源
      最近更新 更多