PyYAML可以以非字母顺序转储dict项吗?

时间:2013-05-28 00:15:27

标签: python dictionary yaml pyyaml

我正在使用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,但我无法弄清楚它是否能够更改项目顺序。

10 个答案:

答案 0 :(得分:39)

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


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

答案 1 :(得分:13)

现在,如果您将PyYAML升级到5.1版本,它将支持转储,而无需按以下方式对键进行排序:

new_dataframe <- cbind(bla, StreamsTempArimadf)

这是非常新的东西,只是在几个小时前我输入时修复的。

答案 2 :(得分:9)

一线统治他们:

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)

答案 3 :(得分: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中项目的顺序。

另一种解决方案在this post中给出。它与@ Blender类似,但适用于safe_dump。常见的元素是将dict转换为元组列表,因此if hasattr(mapping, 'items')检查的计算结果为false。

更新:

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

答案 4 :(得分:2)

根据需要,您需要做两件事:

  • 您需要使用除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解析器完成的,我是作者。

答案 5 :(得分:1)

对于Python 3.7+,字典保留插入顺序。最好使用一个对此尊重的库,例如我的项目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'

答案 6 :(得分:1)

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

yaml.representer.SafeRepresenter.add_representer(
    OrderedDict, ordered_dict_representer
)

答案 7 :(得分:-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

答案 8 :(得分:-1)

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

yaml.sort_base_mapping_type_on_output = False

答案 9 :(得分:-2)

以@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身份函数排序的内置函数即可。