80

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 不(似乎)支持它。我还查看了 subclassing yaml.Dumper,但我无法弄清楚它是否能够更改项目顺序。

4

9 回答 9

183

如果您将 PyYAML 升级到 5.1 版本,现在它支持转储而不对键进行排序,如下所示:

yaml.dump(data, sort_keys=False)

如图所示help(yaml.Dumper)sort_keys默认为True

Dumper(stream, default_style=None, default_flow_style=False,
  canonical=None, indent=None, width=None, allow_unicode=None,
  line_break=None, encoding=None, explicit_start=None, explicit_end=None,
  version=None, tags=None, sort_keys=True)

(这些作为 kwargs 传递给yaml.dump

于 2019-03-14T20:27:19.810 回答
47

可能有更好的解决方法,但我在文档或源代码中找不到任何内容。


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类。

于 2013-05-28T00:42:23.313 回答
18

对于 Python 3.7+,dicts 保留插入顺序。从 PyYAML 5.1.x 开始,您可以禁用键的排序 ( #254 )。不幸的是,排序键的行为仍然默认为True.

>>> import yaml
>>> yaml.dump({"b":1, "a": 2})
'a: 2\nb: 1\n'
>>> yaml.dump({"b":1, "a": 2}, sort_keys=False)
'b: 1\na: 2\n'

我的项目oyaml是 PyYAML 的 monkeypatch/drop-in 替代品。默认情况下,它将在所有 Python 版本和 PyYAML 版本中保留字典顺序。

>>> import oyaml as yaml  # pip install oyaml
>>> yaml.dump({"b":1, "a": 2})
'b: 1\na: 2\n'

此外,它会将子类转储collections.OrderedDict为普通映射,而不是 Python 对象。

>>> from collections import OrderedDict
>>> d = OrderedDict([("b", 1), ("a", 2)])
>>> import yaml
>>> yaml.dump(d)
'!!python/object/apply:collections.OrderedDict\n- - - b\n    - 1\n  - - a\n    - 2\n'
>>> yaml.safe_dump(d)
RepresenterError: ('cannot represent an object', OrderedDict([('b', 1), ('a', 2)]))
>>> import oyaml as yaml
>>> yaml.dump(d)
'b: 1\na: 2\n'
>>> yaml.safe_dump(d)
'b: 1\na: 2\n'
于 2018-09-11T03:09:42.910 回答
14

一条线来统治他们:

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)
于 2018-10-03T07:16:55.407 回答
3

这实际上只是@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.

这篇文章中给出了另一种解决方案。它类似于@Blender,但适用于safe_dump. 共同元素是将 dict 转换为元组列表,因此if hasattr(mapping, 'items')检查结果为 false。

更新:

我刚刚注意到 Fedora 项目的 EPEL 存储库有一个名为 的包python2-yamlordereddictloader,还有一个用于 Python 3 的包。该软件包的上游项目可能是跨平台的。

于 2017-08-31T15:09:11.673 回答
2

你需要做两件事来得到你想要的:

  • 您需要使用 a 以外的其他东西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 解析器,我是其中的作者。

于 2016-08-12T20:20:51.407 回答
0

如果使用safe_dump(即dumpwith Dumper=SafeDumper),则调用yaml.add_representer无效。在这种情况下,有必要add_representer在类上显式调用方法SafeRepresenter

yaml.representer.SafeRepresenter.add_representer(
    OrderedDict, ordered_dict_representer
)
于 2019-06-28T14:00:06.310 回答
-1

我也在寻找“如何在保留顺序的情况下转储映射?”这个问题的答案。我无法遵循上面给出的解决方案,因为我是 pyyaml 和 python 的新手。在 pyyaml 文档和其他论坛上花了一些时间后,我发现了这一点。

您可以使用标签

!!omap

通过保留顺序来转储映射。如果您想按顺序玩,我认为您必须选择键:值

下面的链接有助于更好地理解。

https://bitbucket.org/xi/pyyaml/issue/13/loading-and-then-dumping-an-omap-is-broken

http://yaml.org/type/omap.html

于 2013-10-01T00:00:22.863 回答
-1

以下设置确保内容未在输出中排序:

yaml.sort_base_mapping_type_on_output = False
于 2020-06-11T08:10:32.410 回答