我正在使用yaml.dump
输出一个字典。它根据键按字母顺序打印出每个项目。
>>> d = {"z":0,"y":0,"x":0}
>>> yaml.dump( d, default_flow_style=False )
'x: 0\ny: 0\nz: 0\n'
有没有办法控制键/值对的顺序?
在我的特定用例中,反向打印(巧合)就足够了。但是为了完整性,我正在寻找一个答案,展示如何更精确地控制订单。
我看过使用collections.OrderedDict
,但PyYAML没有(似乎)支持它。我也查看了yaml.Dumper
的子类化,但是我无法弄清楚它是否能够更改项目顺序。
可能有更好的解决方法,但我在文档或源代码中找不到任何内容。
Python 2(见评论)
我将OrderedDict
子类化并使其返回一个不可解决的项目列表:
from collections import OrderedDict
class UnsortableList(list):
def sort(self, *args, **kwargs):
pass
class UnsortableOrderedDict(OrderedDict):
def items(self, *args, **kwargs):
return UnsortableList(OrderedDict.items(self, *args, **kwargs))
yaml.add_representer(UnsortableOrderedDict, yaml.representer.SafeRepresenter.represent_dict)
它似乎工作:
>>> d = UnsortableOrderedDict([
... ('z', 0),
... ('y', 0),
... ('x', 0)
... ])
>>> yaml.dump(d, default_flow_style=False)
'z: 0\ny: 0\nx: 0\n'
Python 3或2(见评论)
您也可以编写一个自定义代表,但我不知道您以后是否会遇到问题,因为我从中删除了一些样式检查代码:
import yaml
from collections import OrderedDict
def represent_ordereddict(dumper, data):
value = []
for item_key, item_value in data.items():
node_key = dumper.represent_data(item_key)
node_value = dumper.represent_data(item_value)
value.append((node_key, node_value))
return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', value)
yaml.add_representer(OrderedDict, represent_ordereddict)
但有了这个,你可以使用原生的OrderedDict
类。
如果你将PyYAML升级到5.1版本,现在它支持dump而不对这些键进行排序:
yaml.dump(data, default_flow_style=False, sort_keys=False)
这是非常新的,几小时前我打字时就修好了。
单行来统治它们:
yaml.add_representer(dict, lambda self, data: yaml.representer.SafeRepresenter.represent_dict(self, data.items()))
而已。最后。经过所有这些年和数小时,强大的represent_dict
已被击败给它dict.items()
而不仅仅是dict
下面是它的工作原理:
这是相关的PyYaml源代码:
if hasattr(mapping, 'items'):
mapping = list(mapping.items())
try:
mapping = sorted(mapping)
except TypeError:
pass
for item_key, item_value in mapping:
为了防止排序,我们只需要一些没有Iterable[Pair]
的.items()
对象。
dict_items
是一个完美的候选人。
以下是如何在不影响yaml模块的全局状态的情况下执行此操作:
#Using a custom Dumper class to prevent changing the global state
class CustomDumper(yaml.Dumper):
#Super neat hack to preserve the mapping key order. See https://stackoverflow.com/a/52621703/1497385
def represent_dict_preserve_order(self, data):
return self.represent_dict(data.items())
CustomDumper.add_representer(dict, CustomDumper.represent_dict_preserve_order)
return yaml.dump(component_dict, Dumper=CustomDumper)
这实际上只是@Blender答案的附录。如果你查看PyYAML
源代码,在representer.py
模块,你会发现这个方法:
def represent_mapping(self, tag, mapping, flow_style=None):
value = []
node = MappingNode(tag, value, flow_style=flow_style)
if self.alias_key is not None:
self.represented_objects[self.alias_key] = node
best_style = True
if hasattr(mapping, 'items'):
mapping = mapping.items()
mapping.sort()
for item_key, item_value in mapping:
node_key = self.represent_data(item_key)
node_value = self.represent_data(item_value)
if not (isinstance(node_key, ScalarNode) and not node_key.style):
best_style = False
if not (isinstance(node_value, ScalarNode) and not node_value.style):
best_style = False
value.append((node_key, node_value))
if flow_style is None:
if self.default_flow_style is not None:
node.flow_style = self.default_flow_style
else:
node.flow_style = best_style
return node
如果你只是删除mapping.sort()
线,那么它维持OrderedDict
中项目的顺序。
this post给出了另一个解决方案。它与@ Blender相似,但适用于safe_dump
。常见的元素是将dict转换为元组列表,因此if hasattr(mapping, 'items')
检查的结果为false。
更新:
我刚刚注意到Fedora项目的EPEL repo有一个名为python2-yamlordereddictloader
的软件包,还有一个用于Python 3的软件包。该软件包的上游项目可能是跨平台的。
根据需要,您需要做两件事:
dict
以外的东西,因为它不会保留订购的物品import sys
import ruamel.yaml
from ruamel.yaml.comments import CommentedMap
d = CommentedMap()
d['z'] = 0
d['y'] = 0
d['x'] = 0
ruamel.yaml.round_trip_dump(d, sys.stdout)
输出:
z: 0
y: 0
x: 0
¹这是使用ruamel.yaml和YAML 1.2解析器完成的,我是作者。
对于Python 3.7+,dicts保留了插入顺序。最好使用一个尊重它的库,例如我的项目oyaml
,它是PyYAML的monkeypatch / drop-in替代品:
>>> import oyaml as yaml # pip install oyaml
>>> d = {"z": 0, "y": 0, "x": 0}
>>> yaml.dump(d, default_flow_style=False)
'z: 0\ny: 0\nx: 0\n'
如果使用safe_dump
(即dump
与Dumper=SafeDumper
),那么调用yaml.add_representer
没有效果。在这种情况下,有必要在add_representer
类上显式调用SafeRepresenter
方法:
yaml.representer.SafeRepresenter.add_representer(
OrderedDict, ordered_dict_representer
)
我也在寻找一个问题的答案“如何保留订单保留的映射?”我不能按照上面给出的解决方案,因为我是pyyaml和python的新手。花了一些时间在pyyaml文档和其他论坛后我发现了这一点。
您可以使用标签
OMAP!
通过保留订单来转储映射。如果你想玩订单,我认为你必须去寻找钥匙:价值观
以下链接有助于更好地理解。
https://bitbucket.org/xi/pyyaml/issue/13/loading-and-then-dumping-an-omap-is-broken
以@ orodbhen的答案为基础:
old_sorted = __builtins__['sorted']
__builtins__['sorted'] = lambda x: x
with open(filename, 'w') as outfile:
yaml.dump(f_json, outfile)
__builtins['sorted'] = old_sorted
在使用yaml.dump时,只需替换由lambda标识函数排序的内置函数。