问题
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