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

时间:2019-01-26 12:09:53

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

我正在使用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 :(得分:1)

为了使ruamel.yaml能够转储特定的类,无论您定义了什么,都可以得到它 从标准库中获取,或者从其他地方获取,则需要针对代表者 register 。 (使用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”对象没有属性“ 名称

因此,您必须使用第三种方法 add_representer()。参数some_class_to_yaml是一个函数 那将是 在遇到SomeClass实例时调用,并且该函数被调用 将yaml.representer实例作为第一个参数,并将实际数据 (SomeClass的实例)作为第二个参数。

如果SomeClass是可以递归引用自身(间接)的某种容器类型, 您需要格外小心以应对这种可能性,但对于socket.AF_INET,则没有必要。

到目前为止,特定的数据类型非常重要,您需要确定 how 表示YAML中的类型。安静的时候你会看到 SomeClass的属性用作映射中的键(然后 是获取标签的映射),但有时类型可以是 直接以YAML中可用的非集合类型表示,例如 作为字符串,整数等,对于其他类,将 表示为(带标签的)序列。

打印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中的常量也得到处理。