从 YAML 文件加载嵌套对象的 OOP 样式的最佳实践
Best practice for OOP style loading of nested objects from a YAML file
我有一个描述文字游戏内容的 YAML 文件。它包含填充许多嵌套对象所需的数据。这些是场景、动作、结果和状态更新。一个场景包含很多动作,一个动作有很多可能的结果,一个结果可以导致很多状态更新。
我已经为这些实体中的每一个定义了 类,但我不确定从面向对象的角度创建它们的最佳方法。我应该在一组大的嵌套 for 循环中创建所有内容(例如在游戏的初始阶段),还是应该将数据传递到较低级别 类 并逐个解析它?
我觉得使用前一种方法,对象的构造及其关系会更容易理解。但是,如果是后者,我觉得写单元测试会更容易。
我正在使用安全的 PyYAML 加载器加载它,因此标记为特定 python 个对象是不可行的。
请给出支持和反对每种方法或其他替代方法的理由。
虽然"tagging as particular python objects is not feasable",它
用明确的标签标记它们是可行的,即使在
安全装载机的限制。这不会使加载不安全
您的 YAML 文件,除非从这些标签创建的对象具有不安全的
启动时的副作用。
使用标签还允许您转储(和加载)您的数据结构
即使您在开发的某个时刻有记忆
从多个对象到一个特定的其他对象的引用或
当您的对象具有(间接)对自身的引用时。这些将
使用锚点和别名进行转储,解决起来很重要
在不使用标签的情况下自己倾倒这些东西,就像你一样
需要跟踪已转储的对象。
个人单元测试很容易写。通常你唯一的事情
必须确保你可以创建一个没有动作的场景,然后你可以
测试所有不涉及动作的场景等
PyYAML 可以解析所有 YAML 1.1 规范,但无法加载
所有这些规格。此外,YAML 1.2 已经推出十年了。
两者都可能不是您程序的限制,但我建议
使用ruamel.yaml
,没有这些问题
(免责声明:我是该软件包的作者)。如何 dump/load 类
用 ruamel.yaml
描述 here
import sys
from pathlib import Path
import ruamel.yaml
data_file = Path('data.yaml')
yaml = ruamel.yaml.YAML(typ='safe')
@yaml.register_class
class Scene:
def __init__(self, sc_parm, actions):
self.sc_parm = sc_parm
self.actions = actions
@yaml.register_class
class Action:
def __init__(self, ac_parm1, ac_parm2):
self.ac_parm1 = ac_parm2
self.ac_parm2 = ac_parm2
shared_action = Action('south', 1)
data = [
Scene('sc1_parm_val', []),
Scene('sc2_parm_val', [Action('north', 3), shared_action]),
Scene('sc3_parm_val', [shared_action, Action('west', 2)]),
]
yaml.dump(data, data_file)
print(data_file.read_text(), end='')
print('=+'*30)
d2 = yaml.load(data_file)
for d in d2:
print(d.sc_parm, d.actions)
给出:
- !Scene
actions: []
sc_parm: sc1_parm_val
- !Scene
actions:
- !Action {ac_parm1: 3, ac_parm2: 3}
- &id001 !Action {ac_parm1: 1, ac_parm2: 1}
sc_parm: sc2_parm_val
- !Scene
actions:
- *id001
- !Action {ac_parm1: 2, ac_parm2: 2}
sc_parm: sc3_parm_val
=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
sc1_parm_val []
sc2_parm_val [<__main__.Action object at 0x7f009be56f28>, <__main__.Action object at 0x7f009be56fd0>]
sc3_parm_val [<__main__.Action object at 0x7f009be56fd0>, <__main__.Action object at 0x7f009be56e48>]
从Action
对象的ID可以看出,第二个动作
第二个场景的动作再次与最后一个场景的第一个动作相同
场景(原shared_action
)
请确保,如果您决定自己制作 from_yaml
(而不是
依赖于您通过注册获得的默认值),例如为了限制
被转储的属性,你使用的两步过程
创建您的对象,因为这是允许递归数据所必需的
树中的结构。你可能不会马上需要这个,但它是
相当容易做到,例如描述
here 对于
往返加载器(一个安全加载器,可以保留评论,引用
加载然后转储 YAML 时的样式、别名等)。
我有一个描述文字游戏内容的 YAML 文件。它包含填充许多嵌套对象所需的数据。这些是场景、动作、结果和状态更新。一个场景包含很多动作,一个动作有很多可能的结果,一个结果可以导致很多状态更新。
我已经为这些实体中的每一个定义了 类,但我不确定从面向对象的角度创建它们的最佳方法。我应该在一组大的嵌套 for 循环中创建所有内容(例如在游戏的初始阶段),还是应该将数据传递到较低级别 类 并逐个解析它?
我觉得使用前一种方法,对象的构造及其关系会更容易理解。但是,如果是后者,我觉得写单元测试会更容易。
我正在使用安全的 PyYAML 加载器加载它,因此标记为特定 python 个对象是不可行的。
请给出支持和反对每种方法或其他替代方法的理由。
虽然"tagging as particular python objects is not feasable",它 用明确的标签标记它们是可行的,即使在 安全装载机的限制。这不会使加载不安全 您的 YAML 文件,除非从这些标签创建的对象具有不安全的 启动时的副作用。
使用标签还允许您转储(和加载)您的数据结构 即使您在开发的某个时刻有记忆 从多个对象到一个特定的其他对象的引用或 当您的对象具有(间接)对自身的引用时。这些将 使用锚点和别名进行转储,解决起来很重要 在不使用标签的情况下自己倾倒这些东西,就像你一样 需要跟踪已转储的对象。
个人单元测试很容易写。通常你唯一的事情 必须确保你可以创建一个没有动作的场景,然后你可以 测试所有不涉及动作的场景等
PyYAML 可以解析所有 YAML 1.1 规范,但无法加载
所有这些规格。此外,YAML 1.2 已经推出十年了。
两者都可能不是您程序的限制,但我建议
使用ruamel.yaml
,没有这些问题
(免责声明:我是该软件包的作者)。如何 dump/load 类
用 ruamel.yaml
描述 here
import sys
from pathlib import Path
import ruamel.yaml
data_file = Path('data.yaml')
yaml = ruamel.yaml.YAML(typ='safe')
@yaml.register_class
class Scene:
def __init__(self, sc_parm, actions):
self.sc_parm = sc_parm
self.actions = actions
@yaml.register_class
class Action:
def __init__(self, ac_parm1, ac_parm2):
self.ac_parm1 = ac_parm2
self.ac_parm2 = ac_parm2
shared_action = Action('south', 1)
data = [
Scene('sc1_parm_val', []),
Scene('sc2_parm_val', [Action('north', 3), shared_action]),
Scene('sc3_parm_val', [shared_action, Action('west', 2)]),
]
yaml.dump(data, data_file)
print(data_file.read_text(), end='')
print('=+'*30)
d2 = yaml.load(data_file)
for d in d2:
print(d.sc_parm, d.actions)
给出:
- !Scene
actions: []
sc_parm: sc1_parm_val
- !Scene
actions:
- !Action {ac_parm1: 3, ac_parm2: 3}
- &id001 !Action {ac_parm1: 1, ac_parm2: 1}
sc_parm: sc2_parm_val
- !Scene
actions:
- *id001
- !Action {ac_parm1: 2, ac_parm2: 2}
sc_parm: sc3_parm_val
=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
sc1_parm_val []
sc2_parm_val [<__main__.Action object at 0x7f009be56f28>, <__main__.Action object at 0x7f009be56fd0>]
sc3_parm_val [<__main__.Action object at 0x7f009be56fd0>, <__main__.Action object at 0x7f009be56e48>]
从Action
对象的ID可以看出,第二个动作
第二个场景的动作再次与最后一个场景的第一个动作相同
场景(原shared_action
)
请确保,如果您决定自己制作 from_yaml
(而不是
依赖于您通过注册获得的默认值),例如为了限制
被转储的属性,你使用的两步过程
创建您的对象,因为这是允许递归数据所必需的
树中的结构。你可能不会马上需要这个,但它是
相当容易做到,例如描述
here 对于
往返加载器(一个安全加载器,可以保留评论,引用
加载然后转储 YAML 时的样式、别名等)。