ruamel.yaml的sort_keys等价于什么?

9
我正在尝试使用ruamel.yaml将Python字典转储到YAML文件中。我熟悉json模块的接口,其中漂亮地打印一个字典就像这样简单:
import json
with open('outfile.json', 'w') as f:
    json.dump(mydict, f, indent=4, sort_keys=True)

使用 ruamel.yaml,我已经做到了这一步:

import ruamel.yaml
with open('outfile.yaml', 'w') as f:
    ruamel.yaml.round_trip_dump(mydict, f, indent=2)

但是它似乎不支持sort_keys选项。 ruamel.yaml没有详尽的文档,在Google上搜索"ruamel.yaml sort"或"ruamel.yaml alphabetize"并没有找到我期望的简单程度的内容。
是否有一两行代码可以按排序后的键漂亮地打印YAML文件?
(请注意,我需要整个容器中的键按字母顺序排列,递归下去;仅对顶层进行字母排序是不够的。)
请注意,如果我使用round_trip_dump,键将不会排序;如果我使用safe_dump,输出的内容将不是“YAML风格”(或更重要的是“Kubernetes风格”的)YAML。我不希望在输出中有[]{}
$ pip freeze | grep yaml
ruamel.yaml==0.12.5

$ python
>>> import ruamel.yaml
>>> mydict = {'a':1, 'b':[2,3,4], 'c':{'a':1,'b':2}}
>>> print ruamel.yaml.round_trip_dump(mydict)  # right format, wrong sorting
a: 1
c:
  a: 1
  b: 2
b:
- 2
- 3
- 4

>>> print ruamel.yaml.safe_dump(mydict)  # wrong format, right sorting
a: 1
b: [2, 3, 4]
c: {a: 1, b: 2}
3个回答

7
您需要一些递归函数来处理映射/字典、序列/列表:
import sys
import ruamel.yaml

CM = ruamel.yaml.comments.CommentedMap

yaml = ruamel.yaml.YAML()

data = dict(a=1, c=dict(b=2, a=1), b=[2, dict(e=6, d=5), 4])
yaml.dump(data, sys.stdout)

def rec_sort(d):
    try:
        if isinstance(d, CM):
            return d.sort()
    except AttributeError:
        pass
    if isinstance(d, dict):
        # could use dict in newer python versions
        res = ruamel.yaml.CommentedMap()
        for k in sorted(d.keys()):
            res[k] = rec_sort(d[k])
        return res
    if isinstance(d, list):
        for idx, elem in enumerate(d):
            d[idx] = rec_sort(elem)
    return d

print('---')

yaml.dump(rec_sort(data), sys.stdout)

这将会得到:

a: 1
c:
  b: 2
  a: 1
b:
- 2
- e: 6
  d: 5
- 4
---
a: 1
b:
- 2
- d: 5
  e: 6
- 4
c:
  a: 1
  b: 2

当ruamel.yaml执行往返(加载+转储)时,注释映射是它使用的结构,并且往返旨在保持加载期间键的顺序。

以上应该可以在从已注释的YAML文件加载data时合理地保留映射/序列上的注释。


哦,default_flow_style=False是启用“Kubernetes风格”YAML输出的适当方式吗?我可以尝试一下。 - Quuxplusone
这有点像一个单独的问题,如果您愿意,我可以单独提出来问,但现在我想知道:是否有一种好的方法可以默认按“排序键”,但如果有一个名为“name”的键,则将其放在第一位,同样递归应用于整个结构?如果有这样的方法,我会使用它。 - Quuxplusone
@Quuxplusone 首先将(name,value)对放入其中,或者使用.insert(),如我所示。您还可以编写一个特定的序列化程序,了解name,但由于PyYAML的实现方式(ruamel.yaml仍然遵循),这很难为任何键参数化(该答案需要单独的问题)。 - Anthon
1
@Anthon,您如何递归嵌套的CommentedMap以对所有级别的键进行排序? - SkunkSpinner
@Anthon,由于递归部分已经在问题中提到(再次提问可能会被关闭为此问题的重复),如果您能在这里包含它,那将是非常好的。 - luckydonald
显示剩余2条评论

0

正如 @Anthon 的示例所指出的那样,如果你正在使用 Python 3.7 或更新版本(且不需要支持旧版本),你只需要:

import sys
from ruamel.yaml import YAML

yaml = YAML()

data = dict(a=1, c=dict(b=2, a=1), b=[2, dict(e=6, d=5), 4])

def rec_sort(d):
    if isinstance(d, dict):
        res = dict()
        for k in sorted(d.keys()):
            res[k] = rec_sort(d[k])
        return res
    if isinstance(d, list):
        for idx, elem in enumerate(d):
            d[idx] = rec_sort(elem)
    return d

yaml.dump(rec_sort(data), sys.stdout)

自从那个版本以来,dict 已经被排序。


0

ruamel.yaml 中有一个未记录的 sort() 函数,可以用于解决这个问题的变种:

import sys
import ruamel.yaml

yaml = ruamel.yaml.YAML()

test = """- name: a11
  value: 11
- name: a2
  value: 2
- name: a21
  value: 21
- name: a3
  value: 3
- name: a1
  value: 1"""
test_yml = yaml.load(test)

yaml.dump(test_yml, sys.stdout)

未排序的输出

  - name: a11
    value: 11
  - name: a2
    value: 2
  - name: a21
    value: 21
  - name: a3
    value: 3
  - name: a1
    value: 1

按名称排序
test_yml.sort(lambda x: x['name'])
yaml.dump(test_yml, sys.stdout)

排序后的输出

  - name: a1
    value: 1
  - name: a11
    value: 11
  - name: a2
    value: 2
  - name: a21
    value: 21
  - name: a3
    value: 3

这是递归的吗? - luckydonald
@luckydonald 不是的,而且它对于原始问题并不起作用。 - Anthon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接