PyYAML загружает YAML 1.1 с дубликатами ключей - PullRequest
0 голосов
/ 25 декабря 2018

Я пытаюсь использовать PyYAML для загрузки файлов YAML 1.1 (они не помечены как таковые, но имеют восьмеричное целое значение 0123 вместо 0o123 ).

Я не знаю, как были сгенерированы эти файлы, но одна из проблем заключается в том, что некоторые из этих файлов имеют дубликаты ключей, например:

xxx:
   aaa: 011
   bbb: 012
   ccc: 013
   aaa: 014

Я использую yaml.safe_load() для загрузки этих файлов.

После прочтения документации по YAML, раздел 10.2, я ожидал получить предупреждение, и aaa будет иметь значение 9:

Это ошибка для двух равных ключей дляпоявляются в том же узле сопоставления.В таком случае процессор YAML может продолжить работу, игнорируя вторую пару ключ: значение и выдавая соответствующее предупреждение.

Но я не получаю предупреждения, и значение равно 12.

Это ошибка?Есть ли способ заставить PyYAML выбрать первое значение для ключа?

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

Есть много файлов, часто с дубликатами, вложенными гораздо глубже.Они могут иметь сложные структуры между ключами, и дубликаты ключей также не являются уникальными для сопоставления, в котором они происходят, что является допустимым.Использование awk для исправления этих файлов не сработает.И слишком много, чтобы исправить вручную.

1 Ответ

0 голосов
/ 25 декабря 2018

Я бы сказал, что это ошибка в PyYAML.Код ошибки: здесь :

def construct_mapping(self, node, deep=False):
    if not isinstance(node, MappingNode):
        raise ConstructorError(None, None,
                "expected a mapping node, but found %s" % node.id,
                node.start_mark)
    mapping = {}
    for key_node, value_node in node.value:
        key = self.construct_object(key_node, deep=deep)
        if not isinstance(key, collections.Hashable):
            raise ConstructorError("while constructing a mapping", node.start_mark,
                    "found unhashable key", key_node.start_mark)
        value = self.construct_object(value_node, deep=deep)
        mapping[key] = value
    return mapping

Понятно, что не проверяется, существует ли ключ.Вам нужно было бы создать подкласс Constructor, чтобы создать тот, который имеет construct_mapping() с включенной проверкой:

        if key in mapping:
             warnings.warn(somewarning)
        else:
            mapping[key] = value

, а затем создать Loader, используя этот Constructor.

Возможно, будет проще использовать ruamel.yaml (отказ от ответственности: я являюсь автором этого пакета).Он корректно загружает это, предполагая, что вы отключили DuplicateKeyError и явно задали YAML 1.1 в качестве входного формата:

import sys
import ruamel.yaml

yaml_file = Path('xx.yaml')

yaml = ruamel.yaml.YAML()
yaml.version = (1, 1)
yaml.indent(mapping=3, sequence=2, offset=0)
yaml.allow_duplicate_keys = True
data = yaml.load(yaml_file)
assert data['xxx']['aaa'] == 9
yaml_out = ruamel.yaml.YAML()
yaml_out.dump(data, sys.stdout)

Это дает:

xxx:
  aaa: 9
  bbb: 10
  ccc: 11

Ваши восьмеричные будут преобразованы вдесятичные дроби (обычно эта информация сохраняется, но не при загрузке устаревшего YAML 1.1).PyYAML всегда будет делать это.

...