【问题标题】:How to create a custom yaml mapping dumper for ruamel.yaml?如何为 ruamel.yaml 创建自定义 yaml 映射转储程序?
【发布时间】:2018-03-30 03:14:50
【问题描述】:

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

使用默认 dumper/loader 的示例

class Hero:
    yaml_tag = '!Hero'
    def __init__(self, name, age):
        self.name = name
        self.age = age

然后通过ruamel.yaml添加默认的loader/dumper

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

不使用默认 dumper/loader 的原因

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

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

我不知道在这种情况下如何使默认的转储器/加载器工作。

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

【问题讨论】:

    标签: python yaml ruamel.yaml


    【解决方案1】:

    RoundTripConstructor.construct_mapping 的定义是::

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

    并且它需要知道它需要什么样的映射 构造。 RoundTripDumper 对什么有一些期望 可以附加到这样的对象,所以你最好模仿什么 RoundTripDumper pass 中的例程: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 做的第一件事就是 尝试附加 cmets(如果节点上有可用的)。 我将删除 0.15.55 中的默认值,这样更明智 如果你把它漏掉,就会出错,就像你做的那样。

    【讨论】:

    • maptyp 有哪些不同类型,您会使用哪种类型的普通字典?我很难找到这方面的文档
    • 您可以使用CommentedMap 作为普通字典,以便可以附加 cmets 等。我基本上在新的Loader 中复制了所有(未记录的)PyYAML 方法,并尝试(和print()-inserted)我的方法来掌握数据如何通过解析器流动。
    猜你喜欢
    • 1970-01-01
    • 2011-04-27
    • 1970-01-01
    • 2021-10-22
    • 1970-01-01
    • 2021-09-13
    • 1970-01-01
    • 1970-01-01
    • 2020-05-14
    相关资源
    最近更新 更多