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

yptwkmov  于 2023-04-22  发布在  Python
关注(0)|答案(1)|浏览(182)

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

n6lpvg4x1#

为了让ruamel.yaml能够转储一个特定的类,无论你是定义了它,还是从标准库中获取它,或者从其他地方获取if,你都需要在表示器中 * 注册 * 这个类。(在使用YAML(typ='unsafe')时,这是不必要的,但我假设你不想诉诸于此)。
这种注册可以通过不同的方式完成。假设你已经做了yaml = ruamel.yaml.YAML()yaml = ruamel.yaml.YAML(typ='safe'),并且想要表示SomeClass,你可以:

  • 使用yaml.register_class(SomeClass)。这可能适用于其他类,具体取决于它们的定义方式。
  • class SomeClass:定义之前使用一个装饰器@yaml_object(yaml)@yaml.register_class。这主要在定义自己的类时使用
  • 使用以下命令直接添加表示器:yaml.representer.add_representer(SomeClass, some_class_to_yaml)

前两种方法只是语法上的糖衣包裹在第三种方法的外面,它们会尝试使用一个方法to_yaml和一个类属性yaml_tag(如果有的话),并尝试做一些明智的事情,如果其中任何一种都不可用。
您可以尝试yaml.register(socket.AF_INET),但您会注意到它失败了,因为:
属性错误:“AddressFamily”对象没有属性“name
所以你必须使用add_representer()的第三种方法。参数some_class_to_yaml是一个函数,当遇到SomeClass示例时将被调用,该函数以yaml.representer示例作为第一个参数,以实际数据(SomeClass的示例)作为第二个参数。
如果SomeClass是某种可以递归引用自身(间接)的容器类型,则需要特别注意处理这种可能性,但对于socket.AF_INET则不需要这样做。
具体的数据类型非常重要,你需要决定如何在YAML中表示类型。(然后是Map获取标记),但有时类型可以直接表示为YAML中可用的非集合类型,如string、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中的其他常量也得到了处理。

相关问题