如何在自定义 PyYAML 构造函数中处理递归?
How do I handle recursion in a custom PyYAML constructor?
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
是一个 class,具有瞬态字段 foo
和 bar
,以及非瞬态字段 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
我知道为什么了。我的构造函数不是为递归而构建的。它需要 return 子对象才能完成父对象的构造,当子对象和父对象是同一个对象时会失败。
但显然 PyYAML 具有解决此问题的图形遍历,因为代码段 #1 有效。也许有一次通过构建所有对象,第二次通过填充它们的字段。我的问题是,我的自定义构造函数如何与这些机制相结合?
最好能回答这个问题。但是如果答案是我不能用自定义构造函数来做到这一点,并且有一个不太理想的选择(例如将 YAMLObject
class 混合到我的 Node
class 中) , 那么这个答案也将不胜感激。
对于可能涉及递归的复杂类型(mapping/dict、sequence/list、对象),构造函数无法一次性创建对象。因此,您应该 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 的第一印象是它试图保持某种程度的一致性 interface/behavior 与 JSON (dumps/loads)。
我了解并赞赏 JSON 功能,因为它很容易将 JSON 读入动态构造的类型。然而,我对 JSON 格式本身有疑问,尤其是缺乏对多行字符串、注释和可读性的支持。
使用 PyYAML,我发现将 yaml 反序列化为一个类型非常困难。似乎有很多障碍要跳过,我没有 time/interest 去学习。考虑以下将 JSON 反序列化为类型的代码:
with open(file) as filereader:
json.load(filereader, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
通过使用对象加载挂钩,我可以将字典转换为命名元组。现在 pyyaml 非常擅长将 yaml 转换为字典。我最终应用了这个 hack,我从 yamlfile -> dictionary -> json string -> object 流动,如下所示:
json.loads(json.dumps(yaml.load(filereader)), object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
这一行通过中介 json 翻译将 yaml 文件读入类型对象。在我的例子中,这是一个有价值的 hack,因为替代方案要复杂得多。
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
是一个 class,具有瞬态字段 foo
和 bar
,以及非瞬态字段 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
我知道为什么了。我的构造函数不是为递归而构建的。它需要 return 子对象才能完成父对象的构造,当子对象和父对象是同一个对象时会失败。
但显然 PyYAML 具有解决此问题的图形遍历,因为代码段 #1 有效。也许有一次通过构建所有对象,第二次通过填充它们的字段。我的问题是,我的自定义构造函数如何与这些机制相结合?
最好能回答这个问题。但是如果答案是我不能用自定义构造函数来做到这一点,并且有一个不太理想的选择(例如将 YAMLObject
class 混合到我的 Node
class 中) , 那么这个答案也将不胜感激。
对于可能涉及递归的复杂类型(mapping/dict、sequence/list、对象),构造函数无法一次性创建对象。因此,您应该 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 的第一印象是它试图保持某种程度的一致性 interface/behavior 与 JSON (dumps/loads)。
我了解并赞赏 JSON 功能,因为它很容易将 JSON 读入动态构造的类型。然而,我对 JSON 格式本身有疑问,尤其是缺乏对多行字符串、注释和可读性的支持。
使用 PyYAML,我发现将 yaml 反序列化为一个类型非常困难。似乎有很多障碍要跳过,我没有 time/interest 去学习。考虑以下将 JSON 反序列化为类型的代码:
with open(file) as filereader:
json.load(filereader, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
通过使用对象加载挂钩,我可以将字典转换为命名元组。现在 pyyaml 非常擅长将 yaml 转换为字典。我最终应用了这个 hack,我从 yamlfile -> dictionary -> json string -> object 流动,如下所示:
json.loads(json.dumps(yaml.load(filereader)), object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))
这一行通过中介 json 翻译将 yaml 文件读入类型对象。在我的例子中,这是一个有价值的 hack,因为替代方案要复杂得多。