问题描述
我正在尝试为某些配置对象制作自定义 YAML 转储程序/加载程序.为简单起见,假设我们要将 Hero
类的对象转储到 hero.yml
文件中.
I'm trying to make a custom YAML dumper/loader for some configuration objects. For simplicity, assuming we want to dump a object of class Hero
to a hero.yml
file.
class Hero:
yaml_tag = '!Hero'
def __init__(self, name, age):
self.name = name
self.age = age
然后通过ruamel.yaml
yaml.register_class(Hero)
然后尝试转储和加载:
h = Hero('Saber', 15)
with open('config.yml', 'w') as fout:
yaml.dump(h, fout)
with open('config.yml') as fin:
yaml.load(fin)
效果很好!
但是,当我需要更灵活的行为时,需要自定义 from_yaml
和 to_yaml
方法,就会出现问题.
However, when I need a more flexible behavior, thus a custom from_yaml
and to_yaml
method is necessary, there is problem.
Hero
的实现改为:
class Hero:
yaml_tag = '!Hero'
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def to_yaml(cls, representer, data):
return representer.represent_mapping(cls.yaml_tag,
{'name': data.name, 'age': data.age})
@classmethod
def from_yaml(cls, constructor, node):
print(node) # for debug
value = constructor.construct_mapping(node)
return cls(**value)
翻斗机按预期工作.但是加载未能加载 YAML 文件.一个抛出异常:
The dumper works just as desired. But the load failed to load the YAML file. An Exception is thrown:
243 def check_mapping_key(self, node, key_node, mapping, key, value):
244 # type: (Any, Any, Any, Any, Any) -> None
--> 245 if key in mapping:
246 if not self.allow_duplicate_keys:
247 args = [
TypeError: argument of type 'NoneType' is not iterable
通过标有for debug
的print(node)
行,加载的节点为:
By the print(node)
line marked with for debug
, the node loaded is:
MappingNode(tag='!Hero', value=[(ScalarNode(tag='tag:yaml.org,2002:str', value='name'), ScalarNode(tag='tag:yaml.org,2002:str', value='Saber')), (ScalarNode(tag='tag:yaml.org,2002:str', value='age'), ScalarNode(tag='tag:yaml.org,2002:int', value='15'))])
不使用默认 dumper/loader 的原因
此示例是显示问题的最小案例,在实际案例中,我试图仅转储对象的一部分,例如
Reason of not using default dumper/loader
This sample is a minimal case to show the problem, in real case, I'm trying to dump only part of the object, like
class A:
yaml_tag = '!A'
def __init__(self, name, age):
self.data = {'name': name, 'age': age}
A('Saber', 15)
想要的YAML文件是
!A
name: Saber
age: 15
我不知道在这种情况下如何使默认的转储器/加载器工作.
I do not know how to make the default dumper/loader work in this case.
我的错误在哪里导致失败?如何解决这个问题呢?
Where is my mistake that makes this failed? How to solve this problem?
推荐答案
RoundTripConstructor.construct_mapping
的定义是::
def construct_mapping(self, node, maptyp=None, deep=False)
并且它需要知道它需要什么样的映射构造.RoundTripDumper 对什么有一些期望可以附加到这样的对象,所以你最好模仿什么RoundTripDumper pass 中的例程:CommentedMap
(一个普通的dict
不起作用).
and it needs to know what kind of mapping it is expected to
construct. There is some expectation in the RoundTripDumper on what
can be attached to such an object, so you best of emulating what the
routines in the RoundTripDumper pass: CommentedMap
(a normal
dict
is not going to work).
因此您需要执行以下操作:
So you will need to do something like:
from ruamel.yaml import YAML
from ruamel.yaml.comments import CommentedMap
yaml = YAML()
class Hero:
yaml_tag = '!Hero'
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def to_yaml(cls, representer, data):
return representer.represent_mapping(cls.yaml_tag,
{'name': data.name, 'age': data.age})
@classmethod
def from_yaml(cls, constructor, node):
data = CommentedMap()
constructor.construct_mapping(node, data, deep=True)
return cls(**data)
def __str__(self):
return "Hero(name -> {}, age -> {})".format(self.name, self.age)
yaml.register_class(Hero)
ante_hero = Hero('Saber', 15)
with open('config.yml', 'w') as fout:
yaml.dump(ante_hero, fout)
with open('config.yml') as fin:
post_hero = yaml.load(fin)
print(post_hero)
给出:
Hero(name -> Saber, age -> 15)
上面的工作是因为你的类相对简单,如果它可以有的话递归部分,您需要遵循两步创建过程,与创建的对象的初始产量,以便它可以在递归期间使用.
The above works because your class is relatively simple, if it could have recursive parts, you would need to follow a two-step creation process, with initial yield of the object created, so that it can be used during the recursion.
maptyp
默认为 None
是历史的,它必须是放.例如.construct_mapping
做的第一件事就是尝试附加评论(如果节点上有可用的评论).我将删除 0.15.55 中的默认值,这样更明智如果你把它漏掉,就会出错,就像你做的那样.
That the maptyp
defaults to None
is historical, it must be
set. E.g. one of the first things that construct_mapping
does is
trying to attach comments (if any were available on the node).
I'll remove the default value in 0.15.55, which gives a more sensible
error if you leave it out, like you did.
这篇关于如何为 ruamel.yaml 创建自定义 yaml 映射转储程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!