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

时间:2016-04-14 17:04:54

标签: python pyyaml

我目前正在加载像这样的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])

关于Constructors, Representer, Resolvers的部分 似乎没有解决我希望全局覆盖类的情况 所有字典的构造,而不是特殊标记的字典。

我只需要一个可以被称为对象(节点?)的钩子来创建/完成 有没有一种简单的方法可以执行此操作,还是应该遍历嵌套的词典yaml.load返回给我并自行修复?

2 个答案:

答案 0 :(得分: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:无法使用抽象方法__delitem____getitem____iter____len____setitem__

实例化抽象类FancyDict

我猜你的真实FancyDict已经实现了。

ruamel.yaml是一个支持YAML 1.2的YAML库(我推荐使用它,但后来我是该软件包的作者)。 PyYAML仅支持(大部分)YAML 1.1。更有问题的是,对于Python2和Python3,它有不同的constructor.py文件,因此你可能无法在PyYAML中输入上述代码。

答案 1 :(得分: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
)

使用 this answer 中的解决方案的问题是 PyYaml 将映射转换回 construct_yaml_map 函数上的字典。由于为 SafeLoader 添加了自定义 add_constructor,因此仅替换子类中的此函数是不够的,因此您可以覆盖它以将新的 construct_yaml_map 用于您的类。