3

我正在尝试为某些配置对象制作自定义 YAML 转储器/加载器。为简单起见,假设我们要将类对象转储Herohero.yml文件中。

与默认转储器/加载器一起使用的示例

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)

它完美地工作!

to_yaml但因自定义和from_yaml方法而失败

但是,当我需要更灵活的行为,因此需要自定义from_yamlto_yaml方法时,就会出现问题。

的实现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 文件。抛出异常:

    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

通过print(node)标有 的行for debug,加载的节点是:

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'))])

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

此示例是显示问题的最小案例,在实际案例中,我试图仅转储对象的一部分,例如

class A:
    yaml_tag = '!A'
    def __init__(self, name, age):
        self.data = {'name': name, 'age': age}

所需的 YAML 文件A('Saber', 15)

!A
name: Saber
age: 15

在这种情况下,我不知道如何使默认的倾倒器/装载器工作。

我的错误在哪里导致失败?如何解决这个问题呢?

4

1 回答 1

2

的定义RoundTripConstructor.construct_mapping是:

def construct_mapping(self, node, maptyp=None, deep=False)

它需要知道它期望构建什么样的映射。RoundTripDumper 对可以附加到此类对象的内容有一些期望,因此您最好模拟 RoundTripDumper 中的例程传递的内容:(CommentedMap正常 dict不起作用)。

因此,您需要执行以下操作:

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)

上述工作是因为您的类相对简单,如果它可以具有递归部分,则需要遵循两步创建过程,创建对象的初始产量,以便在递归期间可以使用它。

maptyp默认值是历史的None,必须设置。例如,首先要做的事情之一construct_mapping是尝试附加评论(如果节点上有可用的评论)。我将删除 0.15.55 中的默认值,如果您像以前一样将其省略,则会出现更明智的错误。

于 2018-08-13T17:08:41.597 回答