Подтвердить что ты не робот

Может ли 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, но я не смог выяснить, имеет ли он возможность изменять порядок элементов.

4b9b3361

Ответ 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.