2025, Nov 20 15:02
Порядок ключей в ruamel.yaml и CommentedMap: безопасная итерация и автоправки без потери комментариев
Как ruamel.yaml и CommentedMap сохраняют порядок ключей при итерации, когда на это можно опираться в линтинге YAML и делать автоправки без потери комментариев.
Когда нужно линтить и автоматически исправлять внутренние YAML‑файлы, сохраняя встроенную документацию, оптимальным выбором будет ruamel.yaml. Одна из типичных задач в таких пайплайнах — проверка и соблюдение заданного порядка ключей в отображениях. Отсюда возникает простой вопрос: сохраняется ли порядок вставки при итерации по ключам в ruamel.yaml и можно ли безопасно опираться на это для проверок и автоправок?
Пример, иллюстрирующий вопрос
Предположим, вы загружаете YAML‑документ, в котором нужно сохранить комментарии, и хотите проверить порядок ключей в секции settings. YAML может выглядеть так:
settings:
# foo
key2:
prop: value
# bar
key1:
prop: value
...
И вы хотите привести его к виду:
settings:
# bar
key1:
prop: value
# foo
key2:
prop: value
...
Первым делом стоит посмотреть текущий порядок. Примерный фрагмент на ruamel.yaml может выглядеть так:
loader = YAML()
loader.preserve_quotes = True
doc_map = loader.load(path_ref)
sect = doc_map.get("settings", default=[])
if not isinstance(sect, CommentedMap):
return
ordered_keys = list(sect.keys())
Что происходит под капотом
Для отображений вы получаете объект CommentedMap. Это подкласс ordereddict, который сегодня, в свою очередь, является подклассом collections.OrderedDict с добавленным методом .insert(). Вызов .keys() возвращает CommentedMapKeysView, удерживающий ссылку на исходное отображение. Итерация по этому представлению отдаёт ключи в том порядке, в котором они были вставлены в CommentedMap. Иными словами, формирование списка из .keys() сохраняет порядок вставки.
Есть важный нюанс, связанный с типами. Хотя во многих внутренних конфигурациях ключи в settings действительно строковые, по спецификации YAML любой скаляр или коллекция могут выступать ключом отображения. Поэтому в общем случае не следует жёстко задавать тип ключей как list[str]. Загрузчик корректно сделает круговое преобразование для записей вроде:
[2011, 10, 2]: anniversary
Решение
Опирайтесь на то, что ключи из CommentedMap выдаются в порядке вставки. Если нужно зафиксировать их для валидации или логики переупорядочивания, используйте .keys() и итерируйтесь по ним или приводите к list. Не предполагайте, что это всегда строки. Минимальный шаблон выглядит так:
yml = YAML()
yml.preserve_quotes = True
root_doc = yml.load(src_ref)
conf_section = root_doc.get("settings", default=[])
if isinstance(conf_section, CommentedMap):
key_view = conf_section.keys()
insertion_order = list(key_view)
Если планируете переставлять элементы, можно использовать поведение упорядочивания самой мапы: итерация по key_view отражает текущий порядок вставки — именно то, что нужно для сравнения с желаемой последовательностью.
Почему это важно
Когда вы автоматически исправляете конфигурационные файлы и при этом обязаны сохранить комментарии и форматирование, корректность зависит от предсказуемой семантики порядка. CommentedMap гарантирует, что обход ключей повторяет фактический порядок в документе, поэтому проверки и переписывания не разрушат структуру, которую вы хотите сохранить. Понимание того, что ключи в YAML не обязаны быть строками, также предотвращает скрытые ошибки из‑за неверных предположений о типах на валидных, но редких входных данных.
Выводы
Если вы загружаете YAML с помощью ruamel.yaml и получаете CommentedMap, итерация по его ключам идёт в порядке вставки. Это позволяет безопасно проверять и навязывать порядок ключей без потери комментариев. Просто не сужайте тип ключей до строк, если вы не полностью контролируете формат входных данных: YAML допускает не только простые строковые скаляры в качестве ключей.