Может ли PyYAML сбрасывать элементы в не алфавитном порядке?
Я использую yaml.dump
для вывода dict. Он печатает каждый элемент в алфавитном порядке на основе ключа.
>>> 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
, но я не смог выяснить, имеет ли он возможность изменять порядок элементов.
Ответы
Ответ 1
Вероятно, лучший способ обхода проблемы, но я не мог найти ничего в документации или источнике.
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
.
Ответ 2
Если вы обновите PyYAML до версии 5.1, теперь он поддерживает дамп без сортировки ключей следующим образом:
yaml.dump(data, default_flow_style=False, sort_keys=False)
это очень ново, просто исправлено несколько часов назад, когда я печатал.
Ответ 3
Один-линейный, чтобы управлять ими всеми:
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)
Ответ 4
Это действительно просто добавление к ответу @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
.
Другое решение дается в этом сообщении. Он похож на @Blender, но работает для safe_dump
. Общим элементом является преобразование dict в список кортежей, поэтому if hasattr(mapping, 'items')
оценивается как false.
Обновить:
Я только заметил, что в python2-yamlordereddictloader
Fedora Project EPEL есть пакет под названием python2-yamlordereddictloader
, а также один для Python 3. Предпроектный проект для этого пакета, вероятно, является кросс-платформенным.
Ответ 5
Есть две вещи, которые вам нужно сделать, чтобы получить это, как вы хотите:
- вам нужно использовать что-то еще, чем
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, автором которого я являюсь.
Ответ 6
Для Python 3. 7+, dicts сохраняют порядок вставки. Лучше всего использовать библиотеку, которая учитывает это, например, мой проект oyaml
который является заменой для monkeypatch/drop-in для 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'
Ответ 7
@wackazong
-works идеальный и очень элегантный!
~ Большое спасибо: * ~
Ответ 8
Если используется safe_dump
(то есть, dump
с Dumper=SafeDumper
), то вызов yaml.add_representer
имеет никакого эффекта. В таком случае необходимо вызвать add_representer
метод явно на SafeRepresenter
класса:
yaml.representer.SafeRepresenter.add_representer(
OrderedDict, ordered_dict_representer
)
Ответ 9
Я также искал ответ на вопрос "как сбрасывать отображения с сохранением порядка?" Я не мог следовать приведенному выше решению, так как я новичок в 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
Ответ 10
Основываясь на @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
Просто замените встроенную функцию, отсортированную с помощью функции lambda identity, когда вы используете yaml.dump.