读取YAML文件并创建Python对象

3
我是Python的新手,想要读取一个YAML文件并基于其内容创建Python对象。我正在使用ruamel-yaml库。
在我的情况下,我可能有Python类Message、Signal和Signalgroup等(请参见示例文件)。
我的方法是读取YAML文件,检查每行是否包含给定的关键字,并创建相关的对象并填充数据。我认为这是“老派”的方法,也许有更有效的方法来处理文件。
也许可以使用函数register_class/ rep.创建标签“from_yaml”,但由于键已经被索引,这种方法行不通。
Message1:
Message2:
Message3:

有没有更专业的方法?
# Yaml Testfile

- ModuleName: myTestModule
- Version: 1.0
- ModuleNumbers: [96,97,98,99]


- Message1:
   Name: AO3_
   DLC: 8
   Signal1:
     Name: Temperature
     Length: 16
   Signal2:
     Name: AnalogOut3
     Length: 16
     SignalGroup1:  #Comment
        Name: app_fcex
        Type: Bitfield
        Signal1:
            Name: drive_ready
            Length: 1
        Signal2:
            Name: error_active
            Length: 1
        Signal3:
            Name: warning_active            
            Length: 1
   Signal3:
     Name: Temperatur 2
     Length: 8
     ValueTable:
        Name: TempStates
        Item0:
           Name: INIT
           Value: 1
        Item1:
           Name: RUN
           Value: 2
        Item2:
           Name: DONE
           Value: 3
        Item3:
           Name: ERROR
           Value: 4
- Message2:
   name: AO2_
   object: RX2
   DLC: 8

1
你能否更改YAML文件,以包含触发自动对象创建的标签?为什么from_yaml不能工作,“因为键是索引”这并不太合理。 - Anthon
你好,Anthon,感谢您的帮助!我可以跳过索引,这不是问题,但如果结构相同,那将非常好,因为用户可以很好地阅读它。 但请问应该如何实现呢?步骤1:定义Message、Signal、Signalgroup和Valuetable类 步骤2:实现classmethod def from_yaml(cls, constructor, node): return cls(*node.value.split('-')) 步骤3:使用yaml.register_class(Message)等注册四个类 步骤4:加载yaml文件 步骤5:?? 请问如何处理呢?最好的问候 Gerhard - Gerhard B
1个回答

3

我建议您在YAML文件中使用标签,并放弃使用像Item1Item2这样的键名(替换为带有标记对象的列表)。

很难看出您的数据具有确切的结构,但一个初始步骤可以是创建YAML文档(假定在文件input.yaml中):

- ModuleName: myTestModule
- Version: 1.0
- ModuleNumbers: [96,97,98,99]


- !Message
  Name: AO3_
  DLC: 8
  Signal1:
    Name: Temperature
    Length: 16
  Signal2:
    Name: AnalogOut3
    Length: 16
    SignalGroup1:  #Comment
       Name: app_fcex
       Type: Bitfield
       Signal1:
           Name: drive_ready
           Length: 1
       Signal2:
           Name: error_active
           Length: 1
       Signal3:
           Name: warning_active
           Length: 1
  Signal3:
    Name: Temperatur 2
    Length: 8
    ValueTable:
       Name: TempStates
       items:
       - !Item
         Name: INIT
         Value: 1
       - !Item
         Name: RUN
         Value: 2
       - !Item
         Name: DONE
         Value: 3
       - !Item
         Name: ERROR
         Value: 4
- !Message
  name: AO2_
  object: RX2
  DLC: 8

然后使用以下方式加载:

import sys
import ruamel.yaml


class Item:
    def __init__(self, name=None, value=None):
        self.name = name
        self.value = value

    @classmethod
    def from_yaml(cls, constructor, node):
        for m in constructor.construct_yaml_map(node):
            pass
        return cls(m['Name'], m['Value'])

    def __repr__(self):
        return 'Item(name={.name}, value={.value})'.format(self, self)

class Message:
    def __init__(self, name=None, DLC=None, object=None, signals=None):
        self.name = name
        self.dlc = DLC
        self.object = object
        self.signals = [] if signals is None else signals

    @classmethod
    def from_yaml(cls, constructor, node):
        for m in constructor.construct_yaml_map(node):
            pass
        if 'Name' in m:
            name = m['Name']
        elif 'name' in m:
            name = m['name']
        else:
            name = None
        object = m['object'] if 'object' in m else None
        if 'DLC' in m:
            dlc = m['DLC']
        else:
            dlc = None
        if 'signals' in m:
            signals = m['signals']
        elif 'Signal1' in m:
            x = 1
            signals = []
            while True:
                name = "Signal{}".format(x)
                try:
                    signals.append(m[name])
                except KeyError:
                    break
                x += 1
        else:
            signals = None
        return cls(name, dlc, object, signals)

    def __repr__(self):
        return 'Message(name={}, DLC={}, object={}, signals{})'.format(
            self.name, self.dlc, self.object, '[...]' if self.signals else '[]',
        )

yaml = ruamel.yaml.YAML(typ='safe')
yaml.register_class(Item)
yaml.register_class(Message)
with open('input.yaml') as fp:
    data = yaml.load(fp)

上述内容对关键字的可用性进行了一些有限的检查(例如,将Namename规范化为!Message)。
使用上述内容,print('data')会得到以下结果(手动换行):
[{'ModuleName': 'myTestModule'}, 
 {'Version': 1.0}, 
 {'ModuleNumbers': [96, 97, 98, 99]}, 
  Message(name=Signal4, DLC=8, object=None, signals[...]), 
  Message(name=AO2_, DLC=8, object=RX2, signals[])]

执行print(data[3].signals[2]['ValueTable']['items'][2])会输出:

Item(name=DONE, value=3)

当然,应根据需要添加更多的类。

你好,Anthon,非常感谢你的帮助!哇,那太神奇了。我扩展了yaml文件中的其他嵌套结构,它也可以工作!现在,如果我想检查项目内容是否有效,例如,如果项目“长度”的值是有效整数,或者当前yaml行的语法是否正确,应该在“from_yaml”方法中执行验证,就像你在示例中所做的那样,还是有其他技术可以进行验证,如果失败,则获取有关行号的信息?谢谢,Gerhard - Gerhard B
我会在__init__方法中进行检查,这样当从头创建对象时也会检查你的值。只需在那里引发一个错误,并在from_yaml()中捕获它,那里有原始的node,它具有一个属性start_mark(参见nodes.py),并且具有linecolumn属性(参见error.py)。如果你无法从中解决问题,请发布一个新问题。顺便说一句,如果这个答案解决了你的问题,请标记一下,这样其他人就知道这是一个有效的解决方案(而不必阅读评论)。 - Anthon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接