向 ruamel.yaml 声明数据类型以便它可以表示/序列化它?

pma*_*v99 7 python serialization yaml python-3.x ruamel.yaml

我正在使用 python 库中的函数,该函数返回具有特定数据类型的对象。我想将该对象序列化为 yaml 文件,并且我想使用ruamel.yaml。问题是ruamel.yaml不知道如何序列化函数返回的特定数据类型并引发异常:

RepresenterError: cannot represent an object: <...>
Run Code Online (Sandbox Code Playgroud)

问题是如何“声明”数据类型,ruamel.yaml以便它知道如何处理它。

注意:我不能/我不想对库或任何此类内容进行更改。我只是 API 的使用者。

为了使这一点更加具体,让我们使用以下示例,该示例使用的socket.AF_INET恰好是 anIntEnum但特定的数据类型并不重要。

RepresenterError: cannot represent an object: <...>
Run Code Online (Sandbox Code Playgroud)

这给出了这个错误:

    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
Run Code Online (Sandbox Code Playgroud)

Ant*_*hon 10

为了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_classclass 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时将被调用,并且该函数以实例作为第一个参数和实际数据(实例的实例)来调用SomeClassyaml.representerSomeClass)作为第二个参数来调用该函数。

如果SomeClass某种容器类型可以递归地引用自身(间接),则需要特别小心处理这种可能性,但这socket.AF_INET不是必需的。

到目前为止,特定的数据类型非常重要,您需要决定如何在 YAML 中表示该类型。通常,您会看到 的属性SomeClass用作映射中的键(然后是获取标记的映射),但有时该类型可以直接表示为 YAML 中可用的非集合类型,例如string、int 等,对于其他类,表示为(标记的)序列更有意义。

当您打印时type(socket.AF_INET),您会注意到“SomeClass”实际上是AddressFamilysocket.AF_INET检查using后dir(),您会注意到有一个name属性,它很好地为您提供了一个 string '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)
Run Code Online (Sandbox Code Playgroud)

这使:

sock: !socket AF_INET
Run Code Online (Sandbox Code Playgroud)

确保标记被定义为 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
Run Code Online (Sandbox Code Playgroud)

它运行时不会抛出异常,并显示其他常量socket也被处理。