PyYAML: управлять порядком элементов, вызываемых yaml.load().

У меня есть файл настроек yaml, который создает некоторые записи в БД:

setting1:
  name: [item,item]
  name1: text
anothersetting2:
  name: [item,item]
  sub_setting:
      name :[item,item]

когда я обновляю этот файл с помощью setting3 и регенерирую записи в БД:

import yaml
fh = open('setting.txt', 'r')
setting_list = yaml.load(fh)
for i in setting_list:
    add_to_db[i]

очень важно, чтобы порядок их настроек (номера идентификаторов в db) оставался неизменным каждый раз, когда я добавляю их в db... и settings3 просто добавляется в конец yaml.load(), чтобы его идентификатор не не путайте любые записи, которые уже находятся в базе данных... В данный момент каждый раз, когда я добавляю другую настройку и вызываю yaml.load(), записи загружаются в другом порядке, что приводит к разным идентификаторам. Буду рад любым идеям ;)

EDIT: я следовал советам abarnert и усвоил суть https://gist.github.com/844388

Работает как положено спасибо!


person zzart    schedule 08.11.2012    source источник


Ответы (5)


Спецификация YAML ясно говорит, что порядок ключей в сопоставлении является «деталью представления», на которую нельзя полагаться. Таким образом, ваш файл настроек уже недействителен, если он полагается на сопоставление, и вам было бы намного лучше использовать действительный YAML, если это вообще возможно.

Конечно, YAML является расширяемым, и ничто не мешает вам добавить тип «упорядоченного сопоставления» в ваши файлы настроек. Например:

!omap setting1:
  name: [item,item]
  name1: text
!omap anothersetting2:
  name: [item,item]
  !omap sub_setting:
      name :[item,item]

Вы не указали, какой модуль yaml вы используете. В стандартной библиотеке такого модуля нет, и только на PyPI есть как минимум два пакета, которые предоставляют модули с таким именем. Однако я предполагаю, что это PyYAML, потому что, насколько я знаю, он самый популярный.

Описанное выше расширение легко разбирается с помощью PyYAML. См. http://pyyaml.org/ticket/29:

def omap_constructor(loader, node):
    return loader.construct_pairs(node)
yaml.add_constructor(u'!omap', omap_constructor)

Теперь вместо:

{'anothersetting2': {'name': ['item', 'item'],
  'sub_setting': 'name :[item,item]'},
 'setting1': {'name': ['item', 'item'], 'name1': 'text'}}

Вы получите это:

(('anothersetting2', (('name', ['item', 'item']),
  ('sub_setting', ('name, [item,item]'),))),
 ('setting1', (('name', ['item', 'item']), ('name1', 'text'))))

Конечно, это дает вам tuple ключ-значение tuples, но вы можете легко написать _construct_ordereddict и вместо этого получить OrderedDict. Вы также можете написать репрезентатор, который хранит OrdereredDict объектов как !omaps, если вам нужно не только вводить, но и выводить.

Если вы действительно хотите перехватить PyYAML, чтобы он использовал OrderedDict вместо dict для сопоставлений по умолчанию, это довольно легко сделать, если вы уже работаете непосредственно с объектами парсера, но сложнее, если вы хотите придерживаться высокоуровневого методы удобства. К счастью, указанный выше билет имеет реализацию, которую вы можете использовать. Просто помните, что вы больше не используете настоящий YAML, а его вариант, поэтому любое другое программное обеспечение, работающее с вашими файлами, может и, скорее всего, сломается.

person abarnert    schedule 08.11.2012

Мой проект oyaml представляет собой замену PyYAML, которая будет загружать карты в collections.OrderedDict вместо обычных диктов. . Просто установите его и используйте как обычно — работает как на Python 3, так и на Python 2.

Демо на вашем примере:

>>> import oyaml as yaml  # pip install oyaml
>>> yaml.load('''setting1:
...   name: [item,item]
...   name1: text
... anothersetting2:
...   name: [item,item]
...   sub_setting:
...       name :[item,item]''')
OrderedDict([('setting1',
              OrderedDict([('name', ['item', 'item']), ('name1', 'text')])),
             ('anothersetting2',
              OrderedDict([('name', ['item', 'item']),
                           ('sub_setting', 'name :[item,item]')]))])

Обратите внимание, что если словарь stdlib сохраняет порядок (Python ›= 3.7, CPython ›= 3.6), то oyaml будет использовать обычный словарь.

person wim    schedule 09.05.2018
comment
Этот проект все еще активен и совместим с Python 3.8+? - person quassy; 17.08.2020
comment
@quassy Да и да - person wim; 17.08.2020
comment
Большое спасибо! Это очень полезно! - person Golov Pavel; 16.11.2020

Теперь для этого можно использовать ruaml.yaml.

Из https://pypi.python.org/pypi/ruamel.yaml:

ruamel.yaml — это синтаксический анализатор/эмиттер YAML, который поддерживает двустороннее сохранение комментариев, стиль потока seq/map и порядок ключей карты.

person jan    schedule 18.08.2016
comment
Подождите, но я попробовал, и он возвращает обычный дикт. Сброс дал ключи в неправильном порядке. Не могли бы вы уточнить, как это сделать? - person Nick S; 08.09.2018
comment
Вы должны передать Loader = ruamel.yaml.RoundTripLoader методу load, чтобы сохранить порядок. - person Shayan Salehian; 27.09.2018

Для данного отдельного элемента, который, как известно, является упорядоченным словарем, просто создайте элементы списка и используемые коллекции.OrderedDict:

setting1:
  - name: [item,item]
  - name1: text
anothersetting2:
  - name: [item,item]
  - sub_setting:
      name :[item,item]

import collections
import yaml
fh = open('setting.txt', 'r')
setting_list = yaml.load(fh)

setting1 = collections.OrderedDict(list(x.items())[0] for x in setting_list['setting1'])
person mybrid    schedule 28.07.2017

Последнее, что я слышал, PyYAML не поддерживает это, хотя, вероятно, было бы легко изменить его, чтобы принять словарь или подобный словарю объект в качестве отправной точки.

person dstromberg    schedule 08.11.2012
comment
Это не сработает. Во-первых, документы YAML не обязательно должны быть словарями. Во-вторых, если вы начнете, скажем, с OrderedDict, все подсловари все равно будут dict. Итак, что вам действительно нужно, так это изменить его, чтобы принять другой конструктор для использования вместо dict (и, в идеале, по одному для каждого используемого конструктора, хотя другие не будут так часто полезны). - person abarnert; 09.11.2012