有一些事情并不完全清楚,但假设这不是
只是一个编程练习,我认为你有从某个地方读入的数据
有值对,并且你:
- 希望根据这些对的第一个值进行查找
- 第一个值可以有重复项
- 您需要能够将其转储到 YAML 并从 YAML 加载回来
在下面我假设你的输入数据看起来像下面的 Python,但它可以
当然也来自一些数据文件,或者一些其他可以处理重复的结构(Python dicts 不能):
data = [('a', 1),
('b', 2),
('c', 3),
('c', 4),
('c', 5),
]
Python 中的表示
您表示您将数据存储在具有重复键的普通字典中
转换为__duplicate__c,一般来说这不是一个好的选择
因为它会导致问题。
首先,您不能将方法添加到处理这些特殊的内置字典
键,因此您的代码将在您的程序和/或对这些的测试中浮动
特殊键重复。
其次,这使得表格的原始数据条目:
('__duplicate__X': 42),
没有额外的处理就不可能存储,就像条目一样
(4, 1),
('4', 2),
(4, 3),
('4', 4),
,因为在一般情况下,您必须决定如何处理
非字符串重复值,如果将它们映射到字符串,则会屏蔽字符串版本
那些“钥匙”。
即使您的数据仅限于第一个数据项对
作为一个字符串,最好创建一个dict 类,或者
子类dict,这样你就可以在内部保留一个列表
重复的键并以特殊方式处理它们 cq.需要特别的
访问方法。
YAML 中的表示
您建议转储的方式会导致 YAML 无效。没有其他程序可以简单地加载这些数据。
在现代 YAML (1.2) 中,映射中的键必须是唯一的,如果不是这种情况是错误的。
在现已过时十年的 YAML 1.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_representer 和add_constructor)得到相同的结果
应该可以通过使用 PyYAML 来实现,而不需要继承 BaseConstructor。