2

PyYaml用来输出YAML文件。但它重新排序了我的物品。喜欢以下

>>> yaml.dump({'3':5, '1':3})
"{'1': 3, '3': 5}\n"

我想得到"{'3': 5, '1': 3}\n"。我可以做那件事吗

附言。我试过了collections.OrderedDict。它的输出并不好。喜欢以下

>>> a= collections.OrderedDict()
>>> a['3']=1
>>> a['1']=2
>>> a['5']=2
>>> yaml.dump(a)
"!!python/object/apply:collections.OrderedDict\n- - ['3', 1]\n  - ['1', 2]\n  - ['5', 2]\n"
4

2 回答 2

4

TL; DR:解决方案在两行注释“看这里!” 如果您接受输出将是列表列表,可以在程序中将 YAML 作为 dicts 处理,并在存储的文件/文本中进行排序。

如果您不介意像 !!python/ordered_dict 或 !!omap 这样的丑陋的显式类型乱扔文件,那么您也可以走这条路。我的投票投给了!!omap,但我不确定有多少工具/库支持它(不过,我很确定支持 !!python/ordered_dict 的工具更少)。最终,您要处理两组独立的数据:dict 本身和定义键顺序的元数据。

(在没有 !!python/ordered_dict 或 !!omap 的情况下,有一些半神奇的方法可以在 YAML 中强制一个有序的字典,但它们很脆弱,与字典的定义相矛盾,并且可能会随着底层 YAML 库的发展而中断. 顺便说一下,这种情况对于 JSON 是相同的,因为 YAML 是 JSON 的超集,并且不保证键的顺序——这意味着当符合标准的工具/用户第一次弄乱文件时,变通方法会中断。)

这篇文章的其余部分是示例/验证代码,并解释了为什么会这样。

from __future__ import print_function
import yaml

# Setting up some example data
d = {'name': 'A Project',
     'version': {'major': 1, 'minor': 4, 'patch': 2},
     'add-ons': ['foo', 'bar', 'baz']}

# LOOK HERE!
ordering = ['name', 'version', 'add-ons', 'papayas']
ordered_set = [[x, d[x]] for x in ordering if x in d.keys()]
# In the event you only care about a few keys,
# you can tack the unspecified ones onto the end
# Note that 'papayas' isn't a key. You can establish an ordering that
# includes optional keys by using 'if' as a guard in the list comprehension.

# Demonstration
things = {'unordered.yaml': d, 'ordered.yaml': ordered_set}
for k in things:
    f = open(k, 'w')
    f.write(yaml.dump(things[k], default_flow_style=False, allow_unicode=True))
    f.close()

# Let's check the result
output = []
for k in things:
    f = open(k, 'r')
    output.append(dict(yaml.load(f.read())))
    f.close()

# Should print 'OK'
if output[0] == output[1]:
    print('OK')
else:
    print('Something is wrong')

创建的文件如下所示:

订购的.yaml:

- - name
  - A Project
- - version
  - major: 1
    minor: 4
    patch: 2
- - add-ons
  - - foo
    - bar
    - baz

无序的.yaml:

add-ons:
- foo
- bar
- baz
name: A Project
version:
  major: 1
  minor: 4
  patch: 2

这不会像您希望的那样生成漂亮的 YAML 文档。也就是说,它可以将漂亮的 YAML 作为初始输入(耶!),并且编写从不漂亮、有序 YAML 到漂亮、仍然有序、dict 样式的 YAML 的脚本很简单(我留给你做练习) .

如果您有要保留的键的顺序,请将其写入有序列表/元组。使用该列表生成列表的有序列表(但不是元组列表,因为您将在 YAML 中获得 !!python/tuple 类型,这很糟糕)。将其转储到 YAML。要像往常一样读回它,然后将该结构传递给 dict() ,然后您将返回到您开始使用的原始字典。如果您有一个需要保留其顺序的嵌套结构,您可能必须递归地降低该结构(这在代码中比在散文中解释更容易 - 这是您可能已经知道的)。

在此示例中,我希望文件中首先出现项目“名称”,然后是“版本”编号元素,然后是“附加组件”。通常,当您调用 dump() 时,PyYAML 按字母数字顺序对字典键进行排序,但这并不可靠,因为将来可能会发生变化,并且 YAML 标准中没有任何要求,所以我不能保证不同的 YAML 实用程序会以这种方式做事。“附加组件”在“名称”之前,所以我有一个订购问题。所以我定义了我的顺序,然后打包一个有序列表,然后转储它。

您要求从本质上无序的事物中获得秩序。字典是一个哈希表,内部排序专门用于搜索速度。该顺序是您不应该弄乱的,因为如果明天发现了一种更快的实现字典的方法,则运行时需要在不破坏每个人的代码的情况下实现它,而字典依赖于作为哈希表的有用抽象的每个人的代码。

同理,YAML 不是一种标记语言(毕竟它最初代表“Yaml Ain't a Markup Language”),它是一种数据格式。区别很重要。一些数据是有序的,比如元组和列表;有些不是,比如成袋的键值对(与哈希表略有不同,但在概念上相似)。

我使用这种解决方案的递归版本来保证跨不同 YAML 实现的 YAML 输出,不是为了人类可读性,而是因为我在 YAML 中传递大量数据并且每个记录都必须使用密钥进行签名,并且不确定的顺序可以防止使用字典/哈希时统一签名。

于 2014-03-12T14:08:32.073 回答
2

YAML 映射是无序的,Python dicts 也是如此。读取文件并保持顺序的官方方法是使用!!omap,但这些会在 PyYAML 中转换为元组,并且不像dict/ ordereddict/那样容易更新OrderedDict

如果您已经有一个读取并更新的 yaml 文件,则可以使用我的ruamel.yaml库,该库在往返模式下作为有序字典使用时读取映射,并将它们作为普通映射写出(它还保留注释)。

给出了一个用法示例作为另一个问题的答案。

于 2014-11-24T12:38:47.697 回答