如何为ruamel.yaml创建自定义yaml映射转储程序? [英] How to create a custom yaml mapping dumper for ruamel.yaml?

查看:145
本文介绍了如何为ruamel.yaml创建自定义yaml映射转储程序?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为某些配置对象创建自定义的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_yamlto_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 debugprint(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'))])

不使用默认转储程序/加载程序的原因

此示例是显示问题的最小情况,在实际情况下,我正在尝试仅转储对象的一部分,例如

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文件是

The desired YAML file of A('Saber', 15) is

!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传递中的例程: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映射转储程序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆