Best practice for OOP style loading of nested objects from a YAML file

旧时模样 提交于 2021-01-29 03:39:41

问题


I have a YAML file that describes the content of a text game. It contains the data required to populate many nested objects. These are scenes, actions, outcomes and state updates. A scene contains many actions, an action has many possible outcomes and an outcome can cause many states to update.

I have defined classes for each of these entities, but I am not sure of the best way to create them from an object oriented perspective. Should I be creating everything in one big nested set of for loops (say for example in the init of the game) or should I be passing down the data to the lower level classes and parsing it piece by piece?

With the former approach I feel like the construction of the objects and their relationships would be easier to understand. However, with the latter, I feel it would be easier to write unit tests.

I am loading it using a safe PyYAML loader, so tagging as particular python objects is not feasible.

Please give reasons for and against each approach, or other alternatives.


回答1:


Although "tagging as particular python objects is not feasable", it is feasible to tag them with explicit tags, even within the restrictions of a safe loader. This doesn't make it unsafe to load your YAML file, unless the objects created from those tags have unsafe side-effects on initiation.

Using tags also allows you to dump (and load) the data-structure you have in memory even if you have at some point in your development references from more than one object to one specific other object or when you have objects with (indirect) references to itself. These will be dumped using anchors and aliases, and it is non-trivial to resolve these things yourself when dumping without using tags, as you would need to keep track of already dumped objects.

Unittest for individual can easily be written. Normally the only thing you have to make sure is that you can create a scene without actions, then you can test everything for the scene that doesn't involve actions, etc.

PyYAML can parse all of the YAML 1.1 specification, but cannot load all of that spec. Additionally YAML 1.2 has been out for ten years now. Neither might be a restriction for your program, but I do recommend to use ruamel.yaml, which does not have these problems (disclaimer: I am the author of that package). How to dump/load classes with ruamel.yaml is described 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)

which gives:

- !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>]

As you can see from the IDs of the Action objects, the second action of the second scene is again the same as the first action of the last scene (the original shared_action)

Make sure, that if you decide to make your own from_yaml (instead of relying on the default you get by registering), e.g. in order to limit the attributes that get dumped, that you use the two step process of creating your objects as this is necessary to allow for recursive data structures in your tree. You might not need this right away, but it is rather easy to do and e.g. described here for the round-trip loader (a safe-loader that can preserve comments, quoting styles, alias names etc when loading then dumping YAML).



来源:https://stackoverflow.com/questions/58228199/best-practice-for-oop-style-loading-of-nested-objects-from-a-yaml-file

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!