Return 嵌套字典中的所有键和值
Return all keys along with value in nested dictionary
我正在努力将存在于几个 .yaml
文件中的所有文本放入一个新的单一 YAML 文件中,该文件将包含英语翻译,然后有人可以将其翻译成西班牙语。
每个 YAML 文件都有很多嵌套文本。我想为 YAML 文件中的每个值打印完整的 'path',也就是所有键以及值。这是位于 myproject.section.more_information 文件中的 .yaml
文件的示例输入:
default:
heading: Here’s A Title
learn_more:
title: Title of Thing
url: www.url.com
description: description
opens_new_window: true
这是所需的输出:
myproject.section.more_information.default.heading: Here’s a Title
myproject.section.more_information.default.learn_more.title: Title of Thing
mproject.section.more_information.default.learn_more.url: www.url.com
myproject.section.more_information.default.learn_more.description: description
myproject.section.more_information.default.learn_more.opens_new_window: true
这似乎是一个很好的递归候选者,所以我查看了
等示例
但是,我想保留导致给定值的所有键,而不仅仅是值中的最后一个键。我目前正在使用 PyYAML 来 read/write YAML。
关于如何保存每个键的任何提示,因为我继续检查项目是否是字典,然后 return 与每个值关联的所有键?
您要做的是展平嵌套词典。这将是一个很好的起点:Flatten nested Python dictionaries, compressing keys
事实上,如果您将 sep 参数更改为 .
,我认为顶部答案中的代码片段对您有用。
编辑:
检查这个基于链接的 SO 答案的工作示例 http://ideone.com/Sx625B
import collections
some_dict = {
'default': {
'heading': 'Here’s A Title',
'learn_more': {
'title': 'Title of Thing',
'url': 'www.url.com',
'description': 'description',
'opens_new_window': 'true'
}
}
}
def flatten(d, parent_key='', sep='_'):
items = []
for k, v in d.items():
new_key = parent_key + sep + k if parent_key else k
if isinstance(v, collections.MutableMapping):
items.extend(flatten(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)
results = flatten(some_dict, parent_key='', sep='.')
for item in results:
print(item + ': ' + results[item])
如果你想要它按顺序排列,你将需要一个 OrderedDict。
保留一个简单的字符串列表,作为每个缩进深度的最新键。当您从一行前进到下一行而没有任何变化时,只需更改列表末尾的项目即可。当您 "out-dent" 时,将最后一项从列表中弹出。缩进时,追加到列表中。
然后,每敲一个冒号,对应的键项就是列表中字符串的拼接,比如:
'.'.join(key_list)
这会让您以光荣的速度前进吗?
遍历嵌套字典需要递归,并且通过将 "prefix" 交给 "path" 这可以防止您必须对路径段进行任何操作(如@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
当然,如果您的输入没有双路径,您的输出将不会有任何列表。
通过使用 ruamel.yaml
中的 string_types
和 ordereddict
使 Python2 和 Python3 兼容(您没有指明您使用的是哪个版本) .
ordereddict保留了原来的key排序,当然这取决于文件的处理顺序。如果要对路径进行排序,只需将 dump()
更改为使用:
for path in sorted(self._result):
另请注意,'commented' 词典条目上的注释已保留。
¹ ruamel.yaml 是一个 YAML 1.2 解析器,它在往返过程中保留注释和其他数据(PyYAML 完成 YAML 1.1 的大部分工作)。免责声明:我是 ruamel.yaml
的作者
我正在努力将存在于几个 .yaml
文件中的所有文本放入一个新的单一 YAML 文件中,该文件将包含英语翻译,然后有人可以将其翻译成西班牙语。
每个 YAML 文件都有很多嵌套文本。我想为 YAML 文件中的每个值打印完整的 'path',也就是所有键以及值。这是位于 myproject.section.more_information 文件中的 .yaml
文件的示例输入:
default:
heading: Here’s A Title
learn_more:
title: Title of Thing
url: www.url.com
description: description
opens_new_window: true
这是所需的输出:
myproject.section.more_information.default.heading: Here’s a Title
myproject.section.more_information.default.learn_more.title: Title of Thing
mproject.section.more_information.default.learn_more.url: www.url.com
myproject.section.more_information.default.learn_more.description: description
myproject.section.more_information.default.learn_more.opens_new_window: true
这似乎是一个很好的递归候选者,所以我查看了
但是,我想保留导致给定值的所有键,而不仅仅是值中的最后一个键。我目前正在使用 PyYAML 来 read/write YAML。
关于如何保存每个键的任何提示,因为我继续检查项目是否是字典,然后 return 与每个值关联的所有键?
您要做的是展平嵌套词典。这将是一个很好的起点:Flatten nested Python dictionaries, compressing keys
事实上,如果您将 sep 参数更改为 .
,我认为顶部答案中的代码片段对您有用。
编辑:
检查这个基于链接的 SO 答案的工作示例 http://ideone.com/Sx625B
import collections
some_dict = {
'default': {
'heading': 'Here’s A Title',
'learn_more': {
'title': 'Title of Thing',
'url': 'www.url.com',
'description': 'description',
'opens_new_window': 'true'
}
}
}
def flatten(d, parent_key='', sep='_'):
items = []
for k, v in d.items():
new_key = parent_key + sep + k if parent_key else k
if isinstance(v, collections.MutableMapping):
items.extend(flatten(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)
results = flatten(some_dict, parent_key='', sep='.')
for item in results:
print(item + ': ' + results[item])
如果你想要它按顺序排列,你将需要一个 OrderedDict。
保留一个简单的字符串列表,作为每个缩进深度的最新键。当您从一行前进到下一行而没有任何变化时,只需更改列表末尾的项目即可。当您 "out-dent" 时,将最后一项从列表中弹出。缩进时,追加到列表中。
然后,每敲一个冒号,对应的键项就是列表中字符串的拼接,比如:
'.'.join(key_list)
这会让您以光荣的速度前进吗?
遍历嵌套字典需要递归,并且通过将 "prefix" 交给 "path" 这可以防止您必须对路径段进行任何操作(如@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
当然,如果您的输入没有双路径,您的输出将不会有任何列表。
通过使用 ruamel.yaml
中的 string_types
和 ordereddict
使 Python2 和 Python3 兼容(您没有指明您使用的是哪个版本) .
ordereddict保留了原来的key排序,当然这取决于文件的处理顺序。如果要对路径进行排序,只需将 dump()
更改为使用:
for path in sorted(self._result):
另请注意,'commented' 词典条目上的注释已保留。
¹ ruamel.yaml 是一个 YAML 1.2 解析器,它在往返过程中保留注释和其他数据(PyYAML 完成 YAML 1.1 的大部分工作)。免责声明:我是 ruamel.yaml
的作者