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
も見てきましたが、アイテムの順序を変更できるかどうかはわかりませんでした。
おそらくより良い回避策がありますが、ドキュメントまたはソースには何も見つかりませんでした。
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
クラスを使用できます。
PyYAMLを5.1バージョンにアップグレードすると、次のようにキーをソートせずにダンプをサポートするようになりました。
yaml.dump(data, default_flow_style=False, sort_keys=False)
これは非常に新しく、数時間前に入力するだけで修正されます。
それらすべてを支配するワンライナー:
_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)
_
これは、実際には@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もあります。そのパッケージの上流プロジェクトはクロスプラットフォームである可能性が高いです。
必要に応じてこれを取得するには、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パーサーを使用して行われました。このパーサーの著者です。
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'
safe_dump
(つまり、Dumper=SafeDumper
でdump
)が使用されている場合、yaml.add_representer
を呼び出しても効果はありません。そのような場合、SafeRepresenter
クラスでadd_representer
メソッドを明示的に呼び出す必要があります。
yaml.representer.SafeRepresenter.add_representer(
OrderedDict, ordered_dict_representer
)