pyYAML定制翻车机

2024-05-14 23:17:41 发布

您现在位置:Python中文网/ 问答频道 /正文

我想自定义的方式,一个字典是转储到一个yaml文件。 更确切地说,给出以下字典:

{'a': 1,
 'b': 2,
 '__duplicate__c': [3, 4, 5]}

我想将它转储到一个yaml文件,其中的键以__duplicate__开头,处理如下:

^{pr2}$

我已经编写了一个自定义构造函数子类化yaml.constructor.BaseConstructor和一个自定义加载程序,它可以读取具有重复键的YAML文件,现在我想也可以编写具有重复键的YAML文件。在


Tags: 文件程序yaml字典方式constructorduplicatepr2
1条回答
网友
1楼 · 发布于 2024-05-14 23:17:41

有一些事情还不完全清楚,但假设情况并非如此 只是一个编程练习,我认为你有你从某处读来的数据 有值对,并且您:

  • 想根据这些对的第一个值查找吗
  • 第一个值中可以有重复项
  • 你需要能够将它转储到YAML并从YAML装载回来

在下面,我假设您的输入数据看起来像下面的Python,但是它可以 当然,也可以来自一些数据文件,或者其他一些可以处理重复的结构(Pythondicts不能):

data = [('a', 1),
        ('b', 2),
        ('c', 3),
        ('c', 4),
        ('c', 5),
]

Python中的表示

您表示将数据存储在带有重复键的普通dict中 转换为__duplicate__c,这通常不是一个好的选择 因为这会导致问题。在

首先,您不能将一个方法添加到处理这些特殊的 键,所以你的代码会在你的程序和/或测试中浮动 专用钥匙被复制。在

其次,这使得表单的原始数据条目:

^{pr2}$

不需要额外的处理就无法存储,就像条目一样

      (4, 1),
      ('4', 2),
      (4, 3),
      ('4', 4),

,因为在一般情况下,您必须决定如何处理 非字符串重复值,如果将这些值映射到字符串,则会屏蔽 那些“钥匙”。在

即使您的数据仅限于第一个数据项对 作为字符串,最好创建一个dict类,或者 子类dict,这样可以在内部保留 复制钥匙并以特殊方式处理cq。需要特殊的 访问方法。在

在YAML

您建议的转储方式将导致无效的YAML。没有其他程序可以简单地加载此数据。 在现代YAML(1.2)中,映射中的键必须是唯一的,否则就是错误。 在已经过时十年的yaml1.1规范中,PyYAML是其中的一部分实现, 您可以忽略第二个键,并发出适当的警告。请注意

使用标记转储数据更合适,例如:

!MultiDict
- [c]
- a: 1
  b: 2
  c: [3, 4, 5]

上面显式地需要该标记的处理程序,加载将 如果不处理,则失败。以“YAML”表示作为输入 未修改的PyYAML既不会给出警告错误,也不会加载正确的 值(3)。在

(PyPI上有一个multidict包,但它不能包含非字符串键。)在

import sys
from ruamel.yaml import YAML, yaml_object
from ruamel.yaml.compat import StringIO

yaml = YAML()
yaml.default_flow_style = None

data = [('a', 1),
        ('b', 2),
        ('c', 3),
        ('c', 4),
        ('c', 5),
        (4, 1),
        ('4', 2),
        (4, 3),
        ('4', 4),
]

class MultiDictDuplicateKeyError(Exception):
    pass

@yaml_object(yaml)
class MultiDict(dict):
    """ no support for deletion of keys """
    yaml_tag = '!MultiDict'

    def __init__(self, *args, **kw):
        self.__duplicate__ = []
        if args and not isinstance(args[0], dict):
            dict.__init__(self, **kw)
            for arg in args[0]:
                self[arg[0]] = arg[1]
        else:
            dict.__init__(self, *args, **kw)

    def __setitem__(self, key, value):
        if key in self:
            if key in self.__duplicate__:  # it should already be a list
                dict.__getitem__(self, key).append(value)
            else:
                self.__duplicate__.append(key)
                dict.__setitem__(self, key, [dict.__getitem__(self, key), value])
        else:
             dict.__setitem__(self, key, value)

    def __getitem__(self, key):
        if key in self.__duplicate__:
            raise MultiDictDuplicateKeyError("should use multival(key)")
        return dict.__getitem__(self, key)

    def multival(self, key):
        assert key in self.__duplicate__
        values = dict.__getitem__(self, key)
        assert isinstance(values, list)
        for value in values:
             yield value

    def __str__(self):
        return repr({('__duplicate__' + repr(k) if k in self.__duplicate__ else k): v 
                    for k, v in self.items()})

    @classmethod
    def to_yaml(cls, representer, node):
        return representer.represent_sequence(cls.yaml_tag, [node.__duplicate__, dict(node)])

    @classmethod
    def from_yaml(cls, constructor, node):
        # necessary to deal with recursive data structures
        for y in constructor.construct_yaml_map(node.value[1]):
            pass
        for dup in constructor.construct_yaml_seq(node.value[0]):
            pass
        ret = cls(y)
        ret.__duplicate__ = dup
        return ret

md = MultiDict(data)

print('md', md)
print('='*40)
for k in md:
    try:
        print(repr(k), '->', md[k])
    except MultiDictDuplicateKeyError:
        for v in md.multival(k):
            print(repr(k), '->', v)
print('='*40)
yaml.dump(md, sys.stdout)           
print('='*40)

buf = StringIO()
yaml.dump(md, buf)
rt_data = yaml.load(buf.getvalue())
print('rt:', rt_data)

它给出了:

md {'a': 1, 'b': 2, "__duplicate__'c'": [3, 4, 5], '__duplicate__4': [1, 3], "__duplicate__'4'": [2, 4]}
========================================
'a' -> 1
'b' -> 2
'c' -> 3
'c' -> 4
'c' -> 5
4 -> 1
4 -> 3
'4' -> 2
'4' -> 4
========================================
!MultiDict
- [c, 4, '4']
- a: 1
  b: 2
  c: [3, 4, 5]
  4: [1, 3]
  '4': [2, 4]
========================================
rt: {'a': 1, 'b': 2, "__duplicate__'c'": [3, 4, 5], '__duplicate__4': [1, 3], "__duplicate__'4'": [2, 4]}

我在这里使用ruamel.yaml(免责声明我是该包的作者),但是 经过一些调整(使用add_representeradd_constructor),结果相同 应该可以通过使用PyYAML来实现,而不必对BaseConstructor进行子类化。在

相关问题 更多 >

    热门问题