在yaml文件中存储任意对象的树

2024-04-29 03:35:14 发布

您现在位置:Python中文网/ 问答频道 /正文

我只想使用YAML存储表示配置的专用Python对象,并在需要时将其加载回去。
我的应用程序允许定义由嵌套Python对象构成的场景。
根对象(命名为场景)包含一些属性和对象列表(命名为Level1Type1、Level1Type2,…), 这些Level1对象中的每一个都由一些属性和对象列表组成(将它们命名为Level2Type1、Level2Type2等)。
总而言之,它是一棵树。每个叶子都有属性和一个本身就是叶子的其他对象列表。
此外,只需将对象属性的一部分保存在文件中(动态属性对配置文件不感兴趣)。
我决定明确定义保存哪些属性

阅读谷歌检索的主题为“Python用yaml序列化对象”的文档,给了我一些提示,但让我感到困惑 真正需要什么。
其中大部分都是由安顿提供的(非常感谢他)。它主要解释了我为什么使用ruamel.yaml。
遗憾的是:由于缺乏关于Python任意对象序列化的解释和文档,我在这个问题上花了太多时间

我注意到,当使用YAML文件重新加载场景时,使用对象构造函数创建的场景中的列表对象变成了CommentedSeq对象。
我还想知道我在许多例子中看到的__repr__定义的目标。它是否由序列化机制使用

下面是验证我的应用程序需求的代码

import ruamel.yaml
from io import StringIO

yaml = ruamel.yaml.YAML()

class Base:
    """
    Base class for every class in the tree.
    """
    # class variables can be necessary
    cst_value = "common"

    def __init__(self, elt_name=None, comment=None):
        self.elt_name = elt_name
        self.comment = comment

    def treatment(self):
        raise NotImplementedError('Base class should not be implemented')

@yaml.register_class
class Scenario(Base):

    yaml_tag = u'!Scenario'

    def __init__(self, elt_name=None, comment=None, level1_objs=None):
        super().__init__(elt_name, comment)
        # List of level1 objects: to be saved in yaml file.
        self.level1_objs = [] if level1_objs is None else level1_objs

    @classmethod
    def to_yaml(cls, representer, node):
        dict_representation = {
            'elt_name': node.elt_name,
            'comment': node.comment,
            'level1_objs': node.level1_objs
        }
        return representer.represent_mapping(cls.yaml_tag, dict_representation)

    @classmethod
    def from_yaml(cls, constructor, node):
        m = {}
        for m in constructor.construct_yaml_map(node):
            pass
        elt_name = m['elt_name'] if 'elt_name' in m else None
        comment = m['comment'] if 'comment' in m else None
        level1_objs = m['level1_objs'] if 'level1_objs' in m else None
        return cls(elt_name, comment, level1_objs)

    def treatment(self):
        pass


class BunchOfData:

    def __init__(self):
        self.data_frame = None
        self.data1 = None
        self.data2 = None
        self.data3 = None


@yaml.register_class
class Level1Type1(Base):

    yaml_tag = u'!Level1Type1'

    def __init__(self, elt_name=None, comment=None, level2_objs=None, l1_t1_attr1=None):
        super().__init__(elt_name, comment)
        # List of level2 objects: to be saved in yaml file.
        self.level2_objs = [] if level2_objs is None else level2_objs
        # Attribute: to be saved in yaml file.
        self.l1_t1_attr1 = l1_t1_attr1
        # Dynamic attribute: Not to be saved in yaml file
        self.dyn_data = BunchOfData()

    @classmethod
    def to_yaml(cls, representer, node):
        dict_representation = {
            'elt_name': node.elt_name,
            'comment': node.comment,
            'l1_t1_attr1': node.l1_t1_attr1,
            'level2_objs': node.level2_objs
        }
        return representer.represent_mapping(cls.yaml_tag, dict_representation)

    @classmethod
    def from_yaml(cls, constructor, node):
        m = {}
        for m in constructor.construct_yaml_map(node):
            pass
        elt_name = m['elt_name'] if 'elt_name' in m else None
        comment = m['comment'] if 'comment' in m else None
        level2_objs = m['level2_objs'] if 'level2_objs' in m else None
        l1_t1_attr1 = m['l1_t1_attr1'] if 'l1_t1_attr1' in m else None
        return cls(elt_name, comment, level2_objs, l1_t1_attr1)

    def treatment(self):
        pass


@yaml.register_class
class Level1Type2(Base):

    yaml_tag = u'!Level1Type2'

    def __init__(self, elt_name=None, comment=None, level2_objs=None, l1_t2_attr1=None, l1_t2_attr2=None):
        super().__init__(elt_name, comment)
        # List of level2 objects: to be saved in yaml file.
        self.level2_objs = [] if level2_objs is None else level2_objs
        # Attribute: to be saved in yaml file.
        self.l1_t2_attr1 = l1_t2_attr1
        self.l1_t2_attr2 = l1_t2_attr2
        # Dynamic attribute: Not to be saved in yaml file
        self.dyn_data = BunchOfData()

    @classmethod
    def to_yaml(cls, representer, node):
        dict_representation = {
            'elt_name': node.elt_name,
            'comment': node.comment,
            'level2_objs': node.level2_objs,
            'l1_t2_attr1': node.l1_t2_attr1,
            'l1_t2_attr2': node.l1_t2_attr2
        }
        return representer.represent_mapping(cls.yaml_tag, dict_representation)

    @classmethod
    def from_yaml(cls, constructor, node):
        m = {}
        for m in constructor.construct_yaml_map(node):
            pass
        elt_name = m['elt_name'] if 'elt_name' in m else None
        comment = m['comment'] if 'comment' in m else None
        level2_objs = m['level2_objs'] if 'level2_objs' in m else None
        l1_t2_attr1 = m['l1_t2_attr1'] if 'l1_t2_attr1' in m else None
        l1_t2_attr2 = m['l1_t2_attr2'] if 'l1_t2_attr2' in m else None
        return cls(elt_name, comment, level2_objs, l1_t2_attr1, l1_t2_attr2)

    def treatment(self):
        pass


@yaml.register_class
class Level2Type1(Base):

    yaml_tag = u'!Level2Type1'

    def __init__(self, elt_name=None, comment=None, l2_t1_attr1=None):
        super().__init__(elt_name, comment)
        # Attribute: to be saved in yaml file.
        self.l2_t1_attr1 = l2_t1_attr1

    @classmethod
    def to_yaml(cls, representer, node):
        dict_representation = {
            'elt_name': node.elt_name,
            'comment': node.comment,
            'l2_t1_attr1': node.l2_t1_attr1
        }
        return representer.represent_mapping(cls.yaml_tag, dict_representation)

    @classmethod
    def from_yaml(cls, constructor, node):
        m = {}
        for m in constructor.construct_yaml_map(node):
            pass
        elt_name = m['elt_name'] if 'elt_name' in m else None
        comment = m['comment'] if 'comment' in m else None
        l2_t1_attr1 = m['l2_t1_attr1'] if 'l2_t1_attr1' in m else None
        return cls(elt_name, comment, l2_t1_attr1)

    def treatment(self):
        pass


@yaml.register_class
class Level2Type2(Base):

    yaml_tag = u'!Level2Type2'

    def __init__(self, elt_name=None, comment=None, l2_t2_attr1=None, l2_t2_attr2=None):
        super().__init__(elt_name, comment)
        # Attribute: to be saved in yaml file.
        self.l2_t2_attr1 = l2_t2_attr1
        self.l2_t2_attr2 = l2_t2_attr2

    @classmethod
    def to_yaml(cls, representer, node):
        dict_representation = {
            'elt_name': node.elt_name,
            'comment': node.comment,
            'l2_t2_attr1': node.l2_t2_attr1,
            'l2_t2_attr2': node.l2_t2_attr2
        }
        return representer.represent_mapping(cls.yaml_tag, dict_representation)

    @classmethod
    def from_yaml(cls, constructor, node):
        m = {}
        for m in constructor.construct_yaml_map(node):
            pass
        elt_name = m['elt_name'] if 'elt_name' in m else None
        comment = m['comment'] if 'comment' in m else None
        l2_t2_attr1 = m['l2_t2_attr1'] if 'l2_t2_attr1' in m else None
        l2_t2_attr2 = m['l2_t2_attr2'] if 'l2_t2_attr2' in m else None
        return cls(elt_name, comment, l2_t2_attr1, l2_t2_attr2)

    def treatment(self):
        pass


@yaml.register_class
class Level2Type3(Base):

    yaml_tag = u'!Level2Type3'

    def __init__(self, elt_name=None, comment=None, l2_t3_attr1=None, l2_t3_attr2=None, l2_t3_attr3=None):
        super().__init__(elt_name, comment)
        # Attribute: to be saved in yaml file.
        self.l2_t3_attr1 = l2_t3_attr1
        self.l2_t3_attr2 = l2_t3_attr2
        self.l2_t3_attr3 = l2_t3_attr3

    @classmethod
    def to_yaml(cls, representer, node):
        dict_representation = {
            'elt_name': node.elt_name,
            'comment': node.comment,
            'l2_t3_attr1': node.l2_t3_attr1,
            'l2_t3_attr2': node.l2_t3_attr2,
            'l2_t3_attr3': node.l2_t3_attr3
        }
        return representer.represent_mapping(cls.yaml_tag, dict_representation)

    @classmethod
    def from_yaml(cls, constructor, node):
        m = {}
        for m in constructor.construct_yaml_map(node):
            pass
        elt_name = m['elt_name'] if 'elt_name' in m else None
        comment = m['comment'] if 'comment' in m else None
        l2_t3_attr1 = m['l2_t3_attr1'] if 'l2_t3_attr1' in m else None
        l2_t3_attr2 = m['l2_t3_attr2'] if 'l2_t3_attr2' in m else None
        l2_t3_attr3 = m['l2_t3_attr3'] if 'l2_t3_attr3' in m else None
        return cls(elt_name, comment, l2_t3_attr1, l2_t3_attr2, l2_t3_attr3)

    def treatment(self):
        pass

# Make this run.
test = Scenario("my_scenario", "what a scenario may look like after yaml dump",
                [Level1Type1("l1_t1_object", "I am a Level1 Type1 object", [
                    Level2Type1("l2_t1_object", "I am a Level2 Type1 object", 11211),
                    Level2Type2("l2_t2_object", "I am a Level2 Type2 object", 11221, 11222),
                    Level2Type3("l2_t3_object", "I am a Level2 Type3 object", 11231, 11232, 11233),
                ], 111),
                 Level1Type2("l1_t2_object", "I am a Level1 Type2 object", [
                     Level2Type2("l2_t2_object", "I am a Level2 Type2 object", 12221, 12222),
                     Level2Type1("l2_t1_object", "I am a Level2 Type1 object", 12211),
                     Level2Type3("l2_t3_object", "I am a Level2 Type3 object", 12231, 12232, 12233),
                 ], 121, 122)
                 ]
                )
# serialize
dump_buf = StringIO()
yaml.dump(test, dump_buf)
test_serialized = dump_buf.getvalue()
print(test_serialized)
# deserialize
test_is_back = yaml.load(test_serialized)
print(test_is_back)

生成的yaml文件如下所示:

!Scenario
elt_name: my_scenario
comment: what a scenario may look like after yaml dump
level1_objs:
- !Level1Type1
  elt_name: l1_t1_object
  comment: I am a Level1 Type1 object
  l1_t1_attr1: 111
  level2_objs:
  - !Level2Type1
    elt_name: l2_t1_object
    comment: I am a Level2 Type1 object
    l2_t1_attr1: 11211
  - !Level2Type2
    elt_name: l2_t2_object
  ....


Tags: nameinselfnonenodel1yamldef
1条回答
网友
1楼 · 发布于 2024-04-29 03:35:14

这不是一个代码审查站点,因此我将仅限于真实的和IMO隐含的问题:

至于真正的问题。否,序列化不使用__repr__ 过程,只是为了确保您可以“打印”实例,并获得一些人力资源 可解释的表示,而不是您将得到的<__module__.Type. object at 0xaddress>

至于隐含的问题:你得到的是CommentedSeq而不是“正常” 列表,因为您使用默认的往返装载机/卸载机

yaml = ruamel.yaml.YAML()

该加载程序/转储程序需要能够附加注释(以及锚/别名和 未注册标签的标签信息),它可以这样做 CommentedSeq实例,因为它无法在内置{}上执行此操作

CommentedSeq在大多数方面表现为list,但如果这是一个问题 以这样或那样的方式,或者如果您不需要任何往返功能 (就像您的情况一样),您应该只使用:

yaml = ruamel.yaml.YAML(typ='safe')

(这将为您提供更快但不完全兼容的基于C的YAML 1.1装载机/卸载机)

或使用:

yaml = ruamel.yaml.YAML(typ='safe', pure=True)

这使加载程序/转储程序无需完全往返所需的“开销”,因此加载回list而不是CommentedSeq


即使使用 往返加载器,但这不是微不足道的。但是如果您使用的是YAML文档 对于转储和加载,不应在注释中存储任何信息 只需使用安全的自卸车/装载机

相关问题 更多 >