Как неявно пометить узлы в yaml (PyYAML)

Рассмотрим этот файл yaml:

!my-type
name: My type
items:
  - name: First item
    number: 42
  - name: Second item
    number: 43

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

!my-type
name: My type
items:
  - !my-type-item
    name: First item
    number: 42
  - !my-type-item
    name: Second item
    number: 43

Но такой синтаксис громоздкий и избыточный, поскольку все элементы в этой коллекции относятся к одному типу. И становится очень некрасиво, когда таких предметов сотни. Можно ли пометить эти элементы неявно?

Я рассматривал возможность использования yaml.add_path_resolver, но этот API не похоже должен быть общедоступным или стабильным.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
31
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Спецификация YAML говорит

Resolving the tag of a node must only depend on the following three parameters: (1) the non-specific tag of the node, (2) the path leading from the root to the node and (3) the content (and hence the kind) of the node.

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

Проблема здесь в том, что в Python нет классов с объявленными типизированными полями. Языки, в которых они есть, могут проверять их и неявно загружать данные соответствующего типа (сделано SnakeYAML, go-yaml и др.). С PyYAML для этого вам нужно реализовать собственный конструктор, например:

import yaml

def get_value(node, name):
    assert isinstance(node, yaml.MappingNode)
    for key, value in node.value:
        assert isinstance(key, yaml.ScalarNode)
        if key.value == name:
            return value

class MyTypeItem:
    def __init__(self, name, number):
        self.name, self.number = name, number

    @classmethod
    def from_yaml(cls, loader, node):
        name = get_value(node, "name")
        assert isinstance(name, yaml.ScalarNode)

        number = get_value(node, "number")
        assert isinstance(number, yaml.ScalarNode)

        return MyTypeItem(name.value, int(number.value))

    def __repr__(self):
        return f"MyTypeItem(name = {self.name}, number = {self.number})"

class MyType(yaml.YAMLObject):
    yaml_tag = "!my-type"

    def __init__(self, name, items):
        self.name, self.items = name, items

    @classmethod
    def from_yaml(cls, loader, node):
        name = get_value(node, "name")
        assert isinstance(name, yaml.ScalarNode)

        items = get_value(node, "items")
        assert isinstance(items, yaml.SequenceNode)

        return MyType(name.value,
                [MyTypeItem.from_yaml(loader, n) for n in items.value])

    def __repr__(self):
        return f"MyType(name = {self.name}, items = {self.items})"

input = """
!my-type
name: My type
items:
  - name: First item
    number: 42
  - name: Second item
    number: 43
"""

print(yaml.load(input, yaml.FullLoader))

Это дает вам:

MyType(name=My type, items=[MyTypeItem(name=First item, number=42), MyTypeItem(name=Second item, number=43)])

Только самый верхний класс является производным от yaml.YAMLObject и имеет yaml_tag, так что PyYAML может неявно использовать его для корневого элемента. MyTypeItem.from_yaml вызывается явно из MyType и, следовательно, не требует регистрации в PyYAML (вы, могу, делаете это, чтобы иметь возможность напрямую загружать файлы, содержащие такой элемент).

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

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