web-dev-qa-db-ja.com

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も見てきましたが、アイテムの順序を変更できるかどうかはわかりませんでした。

35
mwcz

おそらくより良い回避策がありますが、ドキュメントまたはソースには何も見つかりませんでした。


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クラスを使用できます。

39
Blender

PyYAMLを5.1バージョンにアップグレードすると、次のようにキーをソートせずにダンプをサポートするようになりました。

yaml.dump(data, default_flow_style=False, sort_keys=False)

これは非常に新しく、数時間前に入力するだけで修正されます。

35
Cooper.Wu

それらすべてを支配するワンライナー:

_yaml.add_representer(dict, lambda self, data: yaml.representer.SafeRepresenter.represent_dict(self, data.items()))
_

それでおしまい。最後に。これらすべての年月を経て、強力な_represent_dict_は、単にdictの代わりにdict.items()を与えることで打ち負かされました

これがどのように機能するかです:

これは関連するPyYamlソースコードです。

_    if hasattr(mapping, 'items'):
        mapping = list(mapping.items())
        try:
            mapping = sorted(mapping)
        except TypeError:
            pass
    for item_key, item_value in mapping:
_

ソートを防ぐには、.items()を持たない_Iterable[Pair]_オブジェクトが必要です。

_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)
_
10
Ark-kun

これは、実際には@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_で機能します。一般的な要素は、辞書をタプルのリストに変換することであるため、if hasattr(mapping, 'items')チェックはfalseと評価されます。

更新:

The Fedora ProjectのEPELリポジトリには_python2-yamlordereddictloader_というパッケージがあり、Python 3もあります。そのパッケージの上流プロジェクトはクロスプラットフォームである可能性が高いです。

3
orodbhen

必要に応じてこれを取得するには、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パーサーを使用して行われました。このパーサーの著者です。

2
Anthon

Python 3.7 +、dictsは挿入順序を維持します。私のプロジェクト oyaml これはモンキーパッチです。 PyYAMLのドロップイン置換:

>>> 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'
1
wim

safe_dump (つまり、Dumper=SafeDumperdump)が使用されている場合、yaml.add_representerを呼び出しても効果はありません。そのような場合、SafeRepresenterクラスでadd_representerメソッドを明示的に呼び出す必要があります。

yaml.representer.SafeRepresenter.add_representer(
    OrderedDict, ordered_dict_representer
)
1
Peter Bašista