如何在加载yaml时使用自定义字典类?

问题描述 投票:0回答:3

我当前正在加载这样的 YAML 文件

 import yaml
 yaml.load('''level0:
                 stuff: string0
                 level1: 
                     stuff: string1
                     level2: ...''')

上面的代码创建了嵌套字典。 我想创建

FancyDict
对象的嵌套实例,而不是创建嵌套字典。

class FancyDict(collections.MutableMapping):
   def __init__(self, *args, **kwargs):
       for name in kwargs:
          setattr(self, name, kwargs[name])

关于构造函数、表示器、解析器的部分 似乎没有解决我想全局覆盖该类的情况 构建所有词典而不是特殊标记的词典。

我只需要一个被称为对象(节点?)的钩子被创建/完成。
有没有一种简单的方法可以做到这一点,或者我应该遍历

yaml.load
返回给我的嵌套字典并自己修复它们?

python pyyaml
3个回答
1
投票

那个钩子不存在,构造的类型是硬编码在

construct.BaseConstructor.construct_mapping()
中。

解决这个问题的方法是创建你自己的构造函数并基于你自己的加载程序,并将其作为选项交给

load()

import sys
import collections
import ruamel.yaml as yaml

yaml_str = """\
level0:
  stuff: string0
  level1:
    stuff: string1
    level2: ...
"""

from ruamel.yaml.reader import Reader
from ruamel.yaml.scanner import Scanner
from ruamel.yaml.parser import Parser
from ruamel.yaml.composer import Composer
from ruamel.yaml.constructor import SafeConstructor
from ruamel.yaml.resolver import Resolver
from ruamel.yaml.nodes import MappingNode


class FancyDict(collections.MutableMapping):
    def __init__(self, *args, **kwargs):
        for name in kwargs:
            setattr(self, name, kwargs[name])

    # provide the missing __getitem__, __setitem__, __delitem__, __iter__, and __len__.

class MyConstructor(SafeConstructor):
    def construct_mapping(self, node, deep=False):
        res = SafeConstructor.construct_mapping(self, node, deep)
        assert isinstance(res, dict)
        return FancyDict(**res)


class MyLoader(Reader, Scanner, Parser, Composer, MyConstructor, Resolver):
    def __init__(self, stream, version=None):
        Reader.__init__(self, stream)
        Scanner.__init__(self)
        Parser.__init__(self)
        Composer.__init__(self)
        MyConstructor.__init__(self)
        Resolver.__init__(self)


data = yaml.load(yaml_str, Loader=MyLoader)

当你运行这个时,你会得到一个错误,FancyDict是一个无法实例化的抽象类:

TypeError:无法使用抽象方法实例化抽象类 FancyDict

__delitem__
__getitem__
__iter__
__len__
__setitem__

我猜你真正的

FancyDict
已经实现了这些。


ruamel.yaml 是一个支持 YAML 1.2 的 YAML 库(我建议使用它,但我是该包的作者)。 PyYAML 仅支持(大部分)YAML 1.1。更成问题的是,它对于 Python2 和 Python3 有不同的

constructor.py
文件,因此您可能无法将上述代码放入 PyYAML 中。


0
投票

我找到了一个真正适用于 PyYaml 的解决方案。

class Loader(yaml.FullLoader):

    def construct_yaml_map(self, node):
        data = MyDictionaryClass()
        yield data
        value = self.construct_mapping(node)
        data.update(value)

Loader.add_constructor(
    'tag:yaml.org,2002:map',
    Loader.construct_yaml_map
)

使用这个答案中的解决方案的问题是 PyYaml 在

construct_yaml_map
函数上将映射转换回字典。仅在子类中替换此函数是不够的,因为为 SafeLoader 添加了自定义
add_constructor
,因此您可以覆盖它以在您的类中使用新的
construct_yaml_map


0
投票

我刚刚发现这样的事情解决了我的问题:

yaml = ruamel.yaml.YAML()
yaml.constructor.yaml_base_dict_type = MyFancyDict
d=yaml.load(open('myfile.yaml').read())

(这是根据我的非常具体的用例改编的,它起作用了,但我不明白为什么它一般不起作用)

© www.soinside.com 2019 - 2024. All rights reserved.