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

如何解决在 yaml 文件中存储任意对象的树

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

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

我注意到,当使用 YAML 文件重新加载场景时,使用对象构造函数创建的场景中的 List 对象变成了 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,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,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,'l1_t1_attr1': node.l1_t1_attr1,'level2_objs': node.level2_objs
        }
        return representer.represent_mapping(cls.yaml_tag,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,level2_objs,l1_t1_attr1)

    def treatment(self):
        pass


@yaml.register_class
class Level1Type2(Base):

    yaml_tag = u'!Level1Type2'

    def __init__(self,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,'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,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,l1_t2_attr1,l1_t2_attr2)

    def treatment(self):
        pass


@yaml.register_class
class Level2Type1(Base):

    yaml_tag = u'!Level2Type1'

    def __init__(self,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,'l2_t1_attr1': node.l2_t1_attr1
        }
        return representer.represent_mapping(cls.yaml_tag,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,l2_t1_attr1)

    def treatment(self):
        pass


@yaml.register_class
class Level2Type2(Base):

    yaml_tag = u'!Level2Type2'

    def __init__(self,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,'l2_t2_attr1': node.l2_t2_attr1,'l2_t2_attr2': node.l2_t2_attr2
        }
        return representer.represent_mapping(cls.yaml_tag,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,l2_t2_attr1,l2_t2_attr2)

    def treatment(self):
        pass


@yaml.register_class
class Level2Type3(Base):

    yaml_tag = u'!Level2Type3'

    def __init__(self,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,'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,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,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",12221,12222),Level2Type1("l2_t1_object",12211),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
  ....

解决方法

这不是代码审查网站,所以我将自己限制在真实的和 IMO 隐含的问题上:

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

至于隐含问题:您得到的是 CommentedSeq 而不是“正常” 列表,因为您使用了默认的往返加载器/转储器

yaml = ruamel.yaml.YAML()

那个加载器/转储器需要能够附加注释(和锚点/别名和 未注册标签的标签信息)某处,它可以这样做 CommentedSeq 实例,因为它无法在内置 list 上执行此操作。

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

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

(这将为您提供更快但不完全兼容的基于 C 的 YAML 1.1 加载器/转储器)

或者使用:

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

它为您提供了加载器/转储器,而无需“开销”来进行完整的往返,从而加载回 list 而不是 CommentedSeq


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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?