如何解决在 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)
!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 举报,一经查实,本站将立刻删除。