Может ли PyYAML сбрасывать элементы dict в не алфавитном порядке?

Я использую yaml.dump для вывода dict. Он выводит каждый элемент в алфавитном порядке на основе ключа.

>>> d = {"z":0,"y":0,"x":0}
>>> yaml.dump( d, default_flow_style=False )
'x: 0ny: 0nz: 0n'

есть ли способ контролировать порядок пар ключ/значение?

в моем конкретном случае использования печать в обратном порядке (по совпадению) была бы достаточно хороша. Для полноты, однако, я ищу ответ, который показывает, как более точно контролировать порядок.

Я посмотрел на использование collections.OrderedDict но PyYAML (кажется) не поддерживает его. Я также посмотрел на подклассы yaml.Dumper, но я не смог выяснить, имеет ли он возможность изменять порядок элементов.

7 ответов


вероятно, есть лучший обходной путь, но я не мог найти ничего в документации или источнике.


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 класса.


есть две вещи, которые нужно сделать, чтобы получить это, как вы хотите:

  • вы должны использовать что-то другое, чем dict, потому что он не держит заказанных товаров
  • вам нужно сбросить эту альтернативу соответствующим образом.1

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

1 это было сделано с помощью ruamel.и YAML парсер YAML 1.2, автором которого я являюсь.


это действительно просто дополнение к ответу @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.

обновление:

Я только что заметил, что EPEL repo проекта Fedora имеет пакет под названием python2-yamlordereddictloader, и есть один для Python 3, а также. Восходящий проект для этого пакета, вероятно, кросс-платформенный.


Я также искал ответ на вопрос "как сбросить сопоставления с сохраненным порядком?"Я не мог следовать приведенному выше решению, поскольку я новичок в pyyaml и python. Потратив некоторое время на документацию pyyaml и другие форумы, я нашел это.

вы можете использовать тег

!!omap

чтобы сбросить сопоставления, сохранив порядок. Если вы хотите играть с порядком, я думаю, вам нужно пойти на ключи: values

ссылки ниже может помочь для лучшего понимания.

https://bitbucket.org/xi/pyyaml/issue/13/loading-and-then-dumping-an-omap-is-broken

http://yaml.org/type/omap.html


для Python 3.7+, дикты сохраняют порядок вставки. Лучше всего использовать библиотеку, которая уважает это, например oyaml:

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

основываясь на ответе @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

просто замените встроенную функцию, отсортированную по лямбда-функции, пока вы используете yaml.свалка.


один-лайнер, чтобы управлять ими всеми:

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)