【问题标题】:Dumping custom class objects to a YAML file将自定义类对象转储到 YAML 文件
【发布时间】:2021-04-25 11:20:36
【问题描述】:

我想要一些关于将自定义类对象转储到 YAML 文件的帮助。我的班级的代表如下所示:

from classes.enum_classes import Priority, Deadline

class Test(yaml.YAMLObject):
    
    yaml_tag = u'!RandomTestClass'
 
    def __init__(self):
        
        self._name = ""
        self._request_id = ""
        self._order = None
        self._priority = Priority.medium
        self._deadline = Deadline.label_c

最后两个参数是不同类的对象,都是Enum派生类。我正在尝试将 Test 类的对象的内容转储到 YAML 输出文件中。我的 Test 类的 __repr__ 方法看起来是这样的:

    def __repr__(self):
        return "(name=%r, request=%r, order=%r, priority=%r, deadline=%r)" % \
               ((str(self.name) + str(self.order)), self.request_id, self.order,
                self.priority.name, self._deadline.name)

处理 PyYAML 文档的构造函数和表示器部分以及那里提供的示例(特别是考虑到作者对某些类变量使用列表),我希望我的 YAML 文件在 @ 中显示实际的显示标签987654328@ 方法,而不是变量名本身。这是我目前看到的:

--- !ContainerClass
_requests: !!python/object/apply:collections.defaultdict
  args:
  - !!python/name:builtins.list ''
  dictitems:
    '108':
    - !RandomTestClass
      _deadline: &id002 !Deadline '3'
      _name: '108.1'
      _order: '1'
      _priority: &id006 !Priority '1'
      _request_id: '108'
    - !RandomTestClass
      _deadline: &id002 !Deadline '3'
      _name: '108.2'
      _order: '2'
      _priority: &id006 !Priority '1'
      _request_id: '108'
_name: TestContainer

我希望它看起来像这样:

---
Requests:
    - name: '108.1'
    - requestID: '108'
    - priority: '1'
    - order: '1'
    - deadline: '3'
    - name: '108.2' <for the second entry in the list and so on>
Name: ContainerClass

__repr__ 方法或其他任何东西都不会导致我想要的输出。所以有两个问题我很想得到一些帮助。

  1. 如何获得理智、可读的表示?我猜我将不得不编写一个自定义表示器,所以如果有人可以指导我一些指针,因为我无法以任何方式找到太多信息。

  2. 摆脱那些指针,或者我们想要在优先级和截止日期旁边调用它们的任何东西。 Priority 和 Deadline 是我上面的__init___ 方法中引用的两个类,它们是Enum 子类。由于它们已经是子类,因此任何将它们子类化为 yaml.YAMLObject 的尝试都会导致 mixins 出现错误。为了解决这个问题,一些帖子建议我这样做:

class Priority(Enum):

    low = 0
    medium = 1
    high = 2


class Deadline(Enum):

    label_a = 0
    label_b = 1
    label_c = 2
    label_d = 3


def priority_enum_representer(dumper, data):
    return dumper.represent_scalar('!Priority', str(data.value))


def deadline_enum_representer(dumper, data):
    return dumper.represent_scalar('!Deadline', str(data.value))


yaml.add_representer(Deadline, deadline_enum_representer)
yaml.add_representer(Priority, priority_enum_representer)

非常感谢任何有关解决这两个问题的信息/指针。为这篇长文道歉,但我了解到,更多信息通常会带来更准确的帮助。

更新:

我的 YAML 文件是根据这些 RandomTestClass 对象的列表编写的,这些对象存储在 defaultdict(list) 中的 ContainerClass 中。

class ContainerClass(yaml.YAMLObject):
    
    yaml_tag = u'ContainerClass'

    def __init__(self):
        
        self._name = ""
        self._requests = defaultdict(list)

    def __repr__(self):
        
        return "(Name=%r, Requests=%r)" % \
               (self._name, str(self._requests))

    @property
    def requests(self):
        return self._requests

    @requests.setter
    def requests(self, new_req, value=None):

        if type(new_req) is dict:
            self._requests = new_req
        else:
            try:
                self._requests[new_req].append(value)
            except AttributeError:
                # This means the key exists, but the value is a non-list
                # entity. Change existing value to list, append new value
                # and reassign to the same key
                list_with_values \
                    = [self._requests[new_req], value]
                self._requests[new_req] = list_with_values            

ContainerClass 包含 Test 对象的实例。在另一个类中,这是我包含__main__ 的代码的入口点,我创建了Test 对象的多个实例,然后将它们存储在一个```ContainerClass`` 对象中并转储到YAML 文件中。

# requisite imports here, based on 
# how the files are stored

from classes.Test import Test
from classes.ContainerClass import ContainerClass

class RunTestClass:

if __name__ == '__main__':
    
    yaml_container = ContainerClass()

    test_object_a = Test()
    test_object_a._name = '108.1'
    test_object_a._order = 1
    test_object_a._request_id = '108'

    yaml_container._name = "TestContainer"
    yaml_container._requests[test_object_a._request_id] = test_object_a

    test_object_b = Test()
    test_object_b._name = '108.2'
    test_object_b._order = 2
    test_object_b._request_id = '108'

    yaml_container._name = "TestContainer"
    yaml_container._requests[test_object_b._request_id] = test_object_b

with open(output_file, mode='w+') as outfile:
    for test_class_object in yaml_container._requests:
        yaml.dump(test_class_object, outfile, default_flow_style=False,
                  explicit_start=True, explicit_end=True)

更新:

将单个合并文件添加到问题中,可执行以复制问题。

import yaml
from enum import Enum
from collections import defaultdict


class Priority(Enum):

    low = 0
    medium = 1
    high = 2


class Deadline(Enum):

    label_a = 0
    label_b = 1
    label_c = 2
    label_d = 3


def priority_enum_representer(dumper, data):
    return dumper.represent_scalar('!Priority', str(data.value))


def deadline_enum_representer(dumper, data):
    return dumper.represent_scalar('!Deadline', str(data.value))


yaml.add_representer(Deadline, deadline_enum_representer)
yaml.add_representer(Priority, priority_enum_representer)


class Test(yaml.YAMLObject):
    
    yaml_tag = u'!RandomTestClass'
 
    def __init__(self):
        
        self._name = ""
        self._request_id = ""
        self._order = None
        self._priority = Priority.medium
        self._deadline = Deadline.label_c

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name

    @property
    def request_id(self):
        return self._request_id

    @request_id.setter
    def request_id(self, r_id):
        self._request_id = r_id

    @property
    def order(self):
        return self._order

    @order.setter
    def order(self, order):
        self._order = order

    @property
    def priority(self):
        return self._priority

    @priority.setter
    def priority(self, priority):
        self._priority = priority

    @property
    def deadline(self):
        return self._deadline

    @deadline.setter
    def deadline(self, deadline):
        self._deadline = deadline

    def __str__(self):
        return self.name + ", " + self._request_id + ", " + str(self.order) + ", " \
               + str(self.priority) + ", " + str(self.deadline)


class ContainerClass(yaml.YAMLObject):
    
    yaml_tag = u'ContainerClass'

    def __init__(self):
        
        self._name = ""
        self._requests = defaultdict(list)

    def __repr__(self):
        
        return "(Name=%r, Requests=%r)" % \
               (self._name, str(self._requests))

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name

    @property
    def requests(self):
        return self._requests

    def set_requests(self, new_req, value=None):

        if type(new_req) is dict:
            self._requests = new_req
        else:
            try:
                self._requests[new_req].append(value)
            except AttributeError:
                # This means the key exists, but the value is a non-list
                # entity. Change existing value to list, append new value
                # and reassign to the same key
                print("Encountered a single value, converting to a list and appending new value")
                list_with_values \
                    = [self._requests[new_req], value]
                self._requests[new_req] = list_with_values


yaml_container = ContainerClass()
yaml_container.name = "TestContainer"

test_object_a = Test()
test_object_a._name = '108.1'
test_object_a._order = 1
test_object_a._request_id = '108'
yaml_container.set_requests(test_object_a.request_id, test_object_a)

test_object_b = Test()
test_object_b._name = '108.2'
test_object_b._order = 2
test_object_b._request_id = '108'
yaml_container.set_requests(test_object_b.request_id, test_object_b)

with open('test.yaml', mode='w+') as outfile:
    yaml.dump(yaml_container, outfile, default_flow_style=False,
              explicit_start=True, explicit_end=True)

【问题讨论】:

  • 请显示实际代码以重现您的输出。给定的 YAML 意味着您不只是序列化 Test 的实例,而是包含它的值,并且不清楚您希望这些周围的值如何以及为什么消失。在任何情况下,__repr__ 不用于使用 YAML 序列化您的类,文档仅使用它来向您展示加载示例中值的漂亮呈现。
  • @flyx:这是我的实际代码,大部分情况下只是隐藏了变量名称。我尝试遵循 PyYAML 文档和示例,但我想我不确定序列化实例是什么意思。我只是使用yaml.dump() 来做到这一点。
  • @flyx:我生成的输出 YAML 文件正在使用我无法修改的其他人的代码,因此需要以我展示的方式复制标签等。如果不是,我最终会破坏他们的代码。
  • 请发布minimal, reproducible example,不要引用test_class_object 等未定义变量。您显示的代码没有解释 !!python/object/apply:collections.defaultdict 的来源,并且通常似乎不是用于生成输出的代码,因为它定义了显式的开始/结束,而您的 YAML 两者都不包含。
  • @flyx:希望这就足够了。可能需要根据环境进行一些小的更改,但这是我可以在不泄露任何实际代码的情况下复制的内容。我在 TestClass 中省略了 getter/setter,我想这不是必需的,因为您可以按原样获取、设置值。

标签: python-3.x yaml pyyaml


【解决方案1】:

有不同的方法来解决这个问题。一般来说,你想在ContainerClass中定义一个classmethodto_yaml会被dumper调用,并在那里实现从原生结构到YAML节点图的转换。

一个最小的解决方案是像普通的dict 一样构造你想要的结构,其中包含list,并告诉转储器使用它而不是真正的结构:

    @classmethod
    def to_yaml(cls, dumper, data):
        rSeq = []
        for value in data._requests.values():
            for t in value:
              rSeq.extend([
                {"name": t._name},
                {"requestID": t._request_id},
                {"priority": t._priority},
                {"order": t._order},
                {"deadline": t._deadline}
              ])
        return dumper.represent_mapping("tag:yaml.org,2002:map",
                {"Requests": rSeq, "Name": data._name})

这会给你

---
Name: TestContainer
Requests:
- name: '108.1'
- requestID: '108'
- priority: &id001 !Priority '1'
- order: 1
- deadline: &id002 !Deadline '2'
- name: '108.2'
- requestID: '108'
- priority: *id001
- order: 2
- deadline: *id002
...

YAML 为优先级和 requestID 生成锚点和别名,因为这些值引用相同的对象。你可以通过这样做来避免这些

yaml.Dumper.ignore_aliases = lambda *args : True

在转储 YAML 之前。

您可以更复杂并迭代属性,而不是对名称进行硬编码,但这超出了此答案的范围。如果您想再次将此 YAML 加载到相同的结构中,则需要添加另一个类方法 from_yaml 来实现反向转换。

【讨论】:

  • 非常感谢!那工作得很好。我将使用它来适应我的实际对象,这些对象比这个例子更复杂一些。对于您提到的后半部分(这超出了本答案的范围),您可以指出我的任何参考吗?非常感谢。
  • @adwaraki This answer 枚举类的所有属性,尽管您还想获取除名称之外的属性(因此,getattr 调用的结果)。在这些属性上,您可以调用 fget(data) 在类实例上执行 getter。
猜你喜欢
  • 2014-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-01
  • 2014-01-09
  • 2023-03-25
  • 2011-03-15
相关资源
最近更新 更多