Объявите тип данных в ruamel.yaml, чтобы он мог представлять / сериализовать его? - PullRequest
0 голосов
/ 26 января 2019

Я использую функцию из библиотеки Python, которая возвращает объект с определенным типом данных.Я хотел бы сериализовать этот объект в файл yaml, и я хотел бы использовать ruamel.yaml .Проблема в том, что ruamel.yaml не знает, как сериализовать определенный тип данных, который возвращает функция, и выдает исключение:

RepresenterError: cannot represent an object: <...>

Вопрос в том, как «объявить» тип данных в ruamel.yamlчтобы он знал, как с этим справиться.

Примечание: я не могу / не хочу вносить изменения в библиотеку или что-то в этом роде.Я всего лишь потребитель API.

Чтобы сделать это более конкретным, давайте воспользуемся следующим примером, в котором используется socket.AF_INET, который является IntEnum но конкретный тип данных не должен быть важным.

import sys
import socket

import ruamel.yaml

def third_party_lib():
    """ Return a dict with our data """
    return {"AF_INET": socket.AF_INET}

yaml = ruamel.yaml.YAML(typ="safe", pure=True)
yaml.dump(third_party_lib(), sys.stdout)

, который дает эту ошибку:

    ruamel.yaml.YAML.dump(self, data, stream, **kw)
  File "/home/feanor/Prog/git/vps-bench/.direnv/python-venv-3.7.2/lib/python3.7/site-packages/ruamel/yaml/main.py", line 439, in dump
    return self.dump_all([data], stream, _kw, transform=transform)
  File "/home/feanor/Prog/git/vps-bench/.direnv/python-venv-3.7.2/lib/python3.7/site-packages/ruamel/yaml/main.py", line 453, in dump_all
    self._context_manager.dump(data)
  File "/home/feanor/Prog/git/vps-bench/.direnv/python-venv-3.7.2/lib/python3.7/site-packages/ruamel/yaml/main.py", line 801, in dump
    self._yaml.representer.represent(data)
  File "/home/feanor/Prog/git/vps-bench/.direnv/python-venv-3.7.2/lib/python3.7/site-packages/ruamel/yaml/representer.py", line 84, in represent
    node = self.represent_data(data)
  File "/home/feanor/Prog/git/vps-bench/.direnv/python-venv-3.7.2/lib/python3.7/site-packages/ruamel/yaml/representer.py", line 111, in represent_data
    node = self.yaml_representers[data_types[0]](self, data)
  File "/home/feanor/Prog/git/vps-bench/.direnv/python-venv-3.7.2/lib/python3.7/site-packages/ruamel/yaml/representer.py", line 359, in represent_dict
    return self.represent_mapping(u'tag:yaml.org,2002:map', data)
  File "/home/feanor/Prog/git/vps-bench/.direnv/python-venv-3.7.2/lib/python3.7/site-packages/ruamel/yaml/representer.py", line 222, in represent_mapping
    node_value = self.represent_data(item_value)
  File "/home/feanor/Prog/git/vps-bench/.direnv/python-venv-3.7.2/lib/python3.7/site-packages/ruamel/yaml/representer.py", line 121, in represent_data
    node = self.yaml_representers[None](self, data)
  File "/home/feanor/Prog/git/vps-bench/.direnv/python-venv-3.7.2/lib/python3.7/site-packages/ruamel/yaml/representer.py", line 392, in represent_undefined
    raise RepresenterError('cannot represent an object: %s' % data)
ruamel.yaml.representer.RepresenterError: cannot represent an object: AddressFamily.AF_INET

1 Ответ

0 голосов
/ 26 января 2019

Чтобы ruamel.yaml мог создавать дамп определенного класса, независимо от того, определяете ли вы его, вы получаете его из стандартной библиотеки или получить, если откуда-то еще, вам нужно зарегистрировать этот класс в представителе. (Это не обязательно при использовании YAML(typ='unsafe'), но я предполагаю, что вы не хотите прибегать к этому).

Эта регистрация может быть сделана разными способами. Если у вас есть сделано yaml = ruamel.yaml.YAML() или yaml = ruamel.yaml.YAML(typ='safe'), и хотите представить SomeClass, вы можете:

  • использовать yaml.register_class(SomeClass). Это может работать на других классах в зависимости о том, как они определены.
  • используйте один из декораторов @yaml_object(yaml) или @yaml.register_class, прямо перед class SomeClass: определение. Это в первую очередь полезно при определении ваших собственных классов
  • добавить представителя напрямую, используя: yaml.representer.add_representer (SomeClass, some_class_to_yaml)

Первые два способа - это просто синтаксический сахар, обернутый вокруг третьего кстати, и они попытаются использовать метод to_yaml и атрибут класса yaml_tag если доступно, и попробуйте сделать что-нибудь разумное, если недоступно.

Вы можете попробовать yaml.register(socket.AF_INET), но вы заметите, что это не удалось, потому что:

AttributeError: у объекта 'AddressFamily' нет атрибута ' name '

Так что вам придется прибегнуть к третьему способу, используя add_representer(). Аргумент some_class_to_yaml является функцией это будет вызывается, когда встречается экземпляр SomeClass, и эта функция вызывается с экземпляром yaml.representer в качестве первого аргумента и с фактическими данными (экземпляр SomeClass) в качестве второго аргумента.

Если SomeClass - это некоторый тип контейнера, который может рекурсивно ссылаться на себя (косвенно), Вы должны быть особенно внимательны при работе с этой возможностью, но для socket.AF_INET это не обязательно.

Конкретный тип данных настолько важен, что вам нужно решить, как представлять тип в YAML. Довольно часто вы увидите, что это атрибуты SomeClass используются в качестве ключей в отображении (а затем его это отображение, которое получает тег), но иногда тип может быть напрямую представлены в не-коллекции типа, доступных в YAML в виде строки, int и т. д., для других классов имеет смысл представлена ​​как (помеченная) последовательность.

Когда вы печатаете type(socket.AF_INET), вы заметите, что SomeClass на самом деле AddressFamily. И после проверки socket.AF_INET с использованием dir() вы заметите, что есть атрибут name и это приятно дает вам строку 'AF_INET', которая может быть использована, чтобы сказать представителю как представить эти данные в виде строки, не прибегая к поиску:

import sys
import socket
import ruamel.yaml


def repr_socket(representer, data):
    return representer.represent_scalar(u'!socket', data.name)

yaml = ruamel.yaml.YAML()
yaml.representer.add_representer(socket.AddressFamily, repr_socket)

data = dict(sock=socket.AF_INET)
yaml.dump(data, sys.stdout)

, что дает:

sock: !socket AF_INET

Убедитесь, что тег определен как Unicode (необходимо, если вы используете Python 2.7).

Если вы также хотите загрузить это, вы можете расширить constructor аналогичным образом. Но на этот раз вы получите Node, который вам нужно конвертировать в AddressFamily экземпляр.

yaml_str = """\
- !socket AF_INET
- !socket AF_UNIX
"""

def constr_socket(constructor, node):
    return getattr(socket, node.value)

yaml.constructor.add_constructor(u'!socket', constr_socket)
data = yaml.load(yaml_str)

assert data[0] == socket.AF_INET
assert data[1] == socket.AF_UNIX

, который работает без исключения и показывает, что другой константы в socket также обрабатываются.

...