Формат дампа PyYAML

Я знаю, что есть несколько вопросов об этом на SO, но я не смог найти то, что искал.

Я использую pyyaml для чтения (.load()) .yml файл, изменить или добавить ключ, а затем записать его (.dump()) снова. Проблема в том, что я хочу сохранить формат файла post-dump, но он меняется.

Например, я редактирую ключ en.test.index.few сказать "Bye" вместо "Hello"

Python:

with open(path, 'r', encoding = "utf-8") as yaml_file:
    self.dict = pyyaml.load(yaml_file)

Затем, после смены ключа:

with open(path, 'w', encoding = "utf-8") as yaml_file:
    dump = pyyaml.dump(self.dict, default_flow_style = False, allow_unicode = True, encoding = None)
    yaml_file.write( dump )

YAML:

До:

en:
  test:
    new: "Bye"
    index:
      few: "Hello"
  anothertest: "Something"

После:

en:
  anothertest: Something
  test:
    index:
      few: Hello
    new: Bye

Есть ли способ сохранить тот же формат? Например, квоты и порядок. Я использую не тот инструмент для этого?

Я знаю, может быть, оригинальный файл не совсем корректен, но я не могу его контролировать (это файл Ruby on Rails i18n).

Большое спасибо.

3 ответа

использованиеruamel.yamlвместо.

Библиотека Бой! Повесть о двух библиотеках

PyYAML фактически мертв и существует уже несколько лет. Чтобы усугубить ситуацию, официальный проект дома на http://pyyaml.org/ похоже, недавно был снят. На этом сайте размещен трекер проблем PyYAML, документация и файлы для загрузки. На момент написания статьи все прошло. Это не что иное как бедственное. Добро пожаловать в очередной день с открытым исходным кодом.

ruamel.yaml активно поддерживается. В отличие от PyYAML, ruamel.yaml поддерживает:

  • YAML <= 1,2. PyYAML поддерживает только YAML <= 1.1. Это жизненно важно, поскольку YAML 1.2 намеренно нарушает обратную совместимость с YAML 1.1 в нескольких крайних случаях. Это обычно было бы плохо. В этом случае это делает YAML 1.2 строгим надмножеством JSON. Поскольку YAML 1.1 не является строгим надмножеством JSON, это хорошая вещь.
  • Сохранение туда и обратно. При звонке yaml.dump() выгрузить словарь, загруженный предыдущим вызовом yaml.load():
    • PyYAML наивно игнорирует все входное форматирование - включая комментарии, упорядочение, цитирование и пробелы. Отбрасывается как большая часть цифрового мусора в ближайшую доступную корзину битов.
    • ruamel.yaml умно уважает все входное форматирование. Все. Вся стилистическая энчилада. Весь литературный шебанг. Все.

Миграция библиотек: след кодовых слез

поскольку ruamel.yaml является вилкой PyYAML и, следовательно, соответствует API PyYAML, переключаясь с PyYAML на ruamel.yaml в существующих приложениях обычно так же просто, как заменить все экземпляры этого:

# This imports PyYAML. Stop doing this.
import yaml

...с этим:

# This imports "ruamel.yaml". Always do this.
from ruamel import yaml

Вот и все.

Никаких других изменений не требуется. yaml.load() а также yaml.dump() функции должны продолжать работать так, как ожидалось, с дополнительными преимуществами поддержки YAML 1.2 и активного получения исправлений ошибок.

Сохранение в оба конца и что оно может сделать для вас

Для обратной совместимости с PyYaml, yaml.load() а также yaml.dump() функции не выполняют сохранение туда и обратно по умолчанию. Для этого явно передайте:

  • Необязательный Loader=ruamel.yaml.RoundTripLoader параметр ключевого слова для yaml.load(),
  • Необязательный Dumper=ruamel.yaml.RoundTripDumper параметр ключевого слова для yaml.dump(),

Пример любезно "позаимствован" из ruamel.yaml документация:

import ruamel.yaml

inp = """\
# example
name:
  # Yet another Great Duke of Hell. He's not so bad, really.
  family: TheMighty
  given: Ashtaroth
"""

code = ruamel.yaml.load(inp, Loader=ruamel.yaml.RoundTripLoader)
code['name']['given'] = 'Astarte'  # Oh no you didn't.

print(ruamel.yaml.dump(code, Dumper=ruamel.yaml.RoundTripDumper), end='')

Сделано. Комментарии, порядок, цитирование и пробелы теперь будут сохранены без изменений.

ТЛ; др

Всегда используйте ruamel.yaml, Никогда не используйте PyYAML. ruamel.yaml жизни. PyYAML - это зловонный труп, гниющий в скальной почве PyPi.

Да здравствует ruamel.yaml,

В моем случае я хочу " если значение содержит { или }иначе ничего. Например:

 en:
   key1: value is 1
   key2: 'value is {1}'

Чтобы выполнить это, скопируйте функцию represent_str() из файла представитель.py в модуле PyYaml и использовать другой стиль, если строка содержит { или }:

def represent_str(self, data):
    tag = None
    style = None
    # Add these two lines:
    if '{' in data or '}' in data:
        style = '"'
    try:
        data = unicode(data, 'ascii')
        tag = u'tag:yaml.org,2002:str'
    except UnicodeDecodeError:
        try:
            data = unicode(data, 'utf-8')
            tag = u'tag:yaml.org,2002:str'
        except UnicodeDecodeError:
            data = data.encode('base64')
            tag = u'tag:yaml.org,2002:binary'
            style = '|'
    return self.represent_scalar(tag, data, style=style)

Чтобы использовать его в своем коде:

import yaml

def represent_str(self, data):
  ...

yaml.add_representer(str, represent_str)

В этом случае нет различий между ключами и значениями, и мне этого достаточно. Если вы хотите другой стиль для ключей и значений, выполните то же самое с функцией represent_mapping

Первый

Для представления данных словаря используется следующий код:

mapping = list(mapping.items())
    try:
        mapping = sorted(mapping)
    except TypeError:
        pass

Вот почему заказ изменился

второй

Информация о том, как был представлен скалярный тип (с двойной кавычкой или нет), теряется при чтении (это основной подход библиотеки)

Резюме

Вы можете создать собственный класс на основе 'Dumper' и перегрузить метод 'present_mapping' для изменения поведения представления словаря.

Для сохранения информации о двойных кавычках для скаляров необходимо также создать собственный класс на основе 'Loader', но я боюсь, что это повлияет и на другие классы и сделает это сложно

Другие вопросы по тегам