从YAML文件加载嵌套对象的OOP样式的最佳做法

问题描述 投票:0回答:1

我有一个描述文字游戏内容的YAML文件。它包含填充许多嵌套对象所需的数据。这些是场景,动作,结果和状态更新。一个场景包含许多动作,一个动作具有许多可能的结果,并且结果可能导致许多状态更新。

我已经为每个实体定义了类,但是我不确定从面向对象的角度来创建它们的最佳方法。我应该在一组大型的for循环中创建所有内容(例如在游戏的初始阶段),还是应该将数据传递给较低级别​​的类并逐段分析?

使用前一种方法,我觉得对象的构造及其之间的关系会更容易理解。但是,对于后者,我觉得编写单元测试会更容易。

我正在使用安全的PyYAML加载器加载它,因此标记为特定的python对象是不可行的。

请提供支持和反对每种方法或其他替代方法的理由。

python python-3.x oop yaml pyyaml
1个回答
0
投票

尽管“标记为特定的python对象是不可行的,但它is可以使用显式标签对其进行标记,即使在安全装载机的限制。这不会导致加载不安全您的YAML文件,除非从这些标记创建的对象不安全引发的副作用。

使用标签还可以转储(和加载)您的数据结构拥有记忆力,即使您处于开发的某个时刻从多个对象到另一个特定对象的引用,或当您拥有带有(间接)引用自身的对象时。这些会使用锚和别名进行转储,解决起来很简单在不使用标签的情况下进行转储时,您可以自己处理这些事情需要跟踪已经转储的对象。

个人的单元测试很容易编写。通常你唯一的一件事必须确保您可以创建一个没有动作的场景,然后您可以测试不涉及动作等场景的所有内容。

PyYAML可以解析所有YAML 1.1规范,但无法加载所有的规格。另外,YAML 1.2已经发布了十年。两者都不是您程序的限制,但我建议使用ruamel.yaml,它没有这些问题(免责声明:我是该软件包的作者)。如何转储/加载类用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。)>

© www.soinside.com 2019 - 2024. All rights reserved.