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

How to use custom dictionary class while loading yaml?

我目前正在加载这样的 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 的部分 似乎没有解决我想全局覆盖 class 的这种情况 构建所有词典而不是特殊标记的词典。

我只需要一个可以称为对象(节点?)的钩子 created/finalized。
有没有一种简单的方法可以做到这一点,还是我应该遍历 yaml.load returns 给我的嵌套字典并自己修复它们?

那个钩子不存在,构造的类型硬编码在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是一个抽象class,无法实例化:

TypeError: Can't instantiate abstract class FancyDict with abstract methods __delitem__, __getitem__, __iter__, __len__, __setitem__

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


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

我找到了一个实际适用于 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 函数上将映射转换回字典。仅在 subclass 中替换此函数是不够的,因为为 SafeLoader 添加了自定义 add_constructor,因此您可以覆盖它以将新的 construct_yaml_map 用于您的 class。