【问题标题】:Declare data type to ruamel.yaml so that it can represent/serialize it?将数据类型声明为 ruamel.yaml 以便它可以表示/序列化它?
【发布时间】:2019-06-20 01:15:30
【问题描述】:

我正在使用 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

【问题讨论】:

  • 你真的应该使用 syaml.dump(third_party_lib(), sys.stdout) 而不是你正在做的事情,因为记录在案的是缓慢且内存效率低下。这里只是分散了真正的问题:AddressFamily没有代表注册(“声明”)

标签: python python-3.x serialization yaml ruamel.yaml


【解决方案1】:

为了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。 在使用dir() 检查socket.AF_INET 之后,您会注意到有一个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 中的常量也会被处理。

【讨论】:

  • @pmav99 这可能不适合另一个问题下的评论。
猜你喜欢
  • 1970-01-01
  • 2020-07-11
  • 1970-01-01
  • 2012-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-16
  • 2012-02-10
相关资源
最近更新 更多