漫步嵌套字典递归递归,并将“前缀”交给“路径”,这样可以防止您必须对路径段进行任何操作(如@Prune)所暗示的。
有几件事情要记住,使这个问题有意思:
- 因为你使用多个文件会导致在多个文件中,你需要处理相同的路径(至少投掷一个错误,否则你可能会丢失数据)。在我的例子中,我生成一个值列表。
- 处理特殊键(非字符串(转换?),空字符串,包含
.
的键)。我的例子报告这些并退出。
使用ruamel.yaml¹示例代码:
import sys
import glob
import ruamel.yaml
from ruamel.yaml.comments import CommentedMap, CommentedSeq
from ruamel.yaml.compat import string_types, ordereddict
class Flatten:
def __init__(self, base):
self._result = ordereddict() # key to list of tuples of (value, comment)
self._base = base
def add(self, file_name):
data = ruamel.yaml.round_trip_load(open(file_name))
self.walk_tree(data, self._base)
def walk_tree(self, data, prefix=None):
"""
this is based on ruamel.yaml.scalarstring.walk_tree
"""
if prefix is None:
prefix = ""
if isinstance(data, dict):
for key in data:
full_key = self.full_key(key, prefix)
value = data[key]
if isinstance(value, (dict, list)):
self.walk_tree(value, full_key)
continue
# value is a scalar
comment_token = data.ca.items.get(key)
comment = comment_token[2].value if comment_token else None
self._result.setdefault(full_key, []).append((value, comment))
elif isinstance(base, list):
print("don't know how to handle lists", prefix)
sys.exit(1)
def full_key(self, key, prefix):
"""
check here for valid keys
"""
if not isinstance(key, string_types):
print('key has to be string', repr(key), prefix)
sys.exit(1)
if '.' in key:
print('dot in key not allowed', repr(key), prefix)
sys.exit(1)
if key == '':
print('empty key not allowed', repr(key), prefix)
sys.exit(1)
return prefix + '.' + key
def dump(self, out):
res = CommentedMap()
for path in self._result:
values = self._result[path]
if len(values) == 1: # single value for path
res[path] = values[0][0]
if values[0][1]:
res.yaml_add_eol_comment(values[0][1], key=path)
continue
res[path] = seq = CommentedSeq()
for index, value in enumerate(values):
seq.append(value[0])
if values[0][1]:
res.yaml_add_eol_comment(values[0][1], key=index)
ruamel.yaml.round_trip_dump(res, out)
flatten = Flatten('myproject.section.more_information')
for file_name in glob.glob('*.yaml'):
flatten.add(file_name)
flatten.dump(sys.stdout)
如果你有一个额外的输入文件:
default:
learn_more:
commented: value # this value has a comment
description: another description
那么结果是:
myproject.section.more_information.default.heading: Here’s A Title
myproject.section.more_information.default.learn_more.title: Title of Thing
myproject.section.more_information.default.learn_more.url: www.url.com
myproject.section.more_information.default.learn_more.description:
- description
- another description
myproject.section.more_information.default.learn_more.opens_new_window: true
myproject.section.more_information.default.learn_more.commented: value # this value has a comment
如果
当然你输入没有双重路径,你的输出不会有任何结果名单。
通过使用string_types
和ordereddict
从ruamel.yaml
使这个Python2和Python3兼容(你不指明你正在使用哪个版本)。
ordereddict保留原始的键排序,但这当然取决于文件的处理顺序。如果你想要的路径进行排序,只是改变dump()
使用:
for path in sorted(self._result):
还要注意,在注释的“注释”字典条目被保留。
¹ ruamel.yaml是YAML 1.2解析器可以保留上往返注释和其他数据(PyYAML确实YAML 1.1的大部分地区)。免责声明:我是ruamel.yaml的作者
对于我的输入可能有的许多边缘情况,这是一个非常深思熟虑的解决方案,谢谢。我特别喜欢它保留评论。 – swellactually