漫步嵌套字典遞歸遞歸,並將「前綴」交給「路徑」,這樣可以防止您必須對路徑段進行任何操作(如@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