在python中读取YAML文件并通过匹配键值对访问数据

在python中读取YAML文件并通过匹配键值对访问数据,python,yaml,Python,Yaml,我正在使用Python开发一个软件,其中我需要读取具有多个级别的YAML文件,如下所示: #Filename: SampleCase.yml %YAML 1.1 VesselTypes: - Name: Escort Tug Length: 32 Breadth: 12.8 Depth: 9 Draughts: - Name: Draught1 Mass: 500 CentreOfGravity: [16.497, 0, 4.3

我正在使用Python开发一个软件,其中我需要读取具有多个级别的YAML文件,如下所示:

#Filename: SampleCase.yml
%YAML 1.1
VesselTypes:
  - Name: Escort Tug
    Length: 32
    Breadth: 12.8
    Depth: 9
    Draughts:
    - Name: Draught1
      Mass: 500
      CentreOfGravity: [16.497, 0, 4.32]
    TowingStaples:
    - Name: Staple1
      Position: [0, 0, 0]
    Thrusters:
    - Name: Port Propeller
      Position: [0, -1, 0]
      MaxRPM: 1800
      MaxPower: 2525
    - Name: Stbd Propeller
      Position: [0, 1, 0]
      MaxRPM: 1800
      MaxPower: 2525
  - Name: Ship    
Vessels:
  - Name: Tug
    VesselType: Escort Tug
    Draught: Draught1
    InitialPosition: [0, 0, 0]
    Orientation: [0, 0, 0]
  - Name: Tanker
    VesselType: Ship
    Draught: Draught1
    InitialPosition: [0, 0, 0]
    Orientation: [0, 0, 0]
    Speed: 8  
这里有两艘船,分别是拖船和油轮。它们有两种船型,“护航拖船”和“船舶”


我可以使用索引号访问存储的数据(例如,
数据[“船舶”][0][“名称”]
),但我想使用匹配键访问它们。例如,我想打印名为“拖船”的船舶左舷螺旋桨的MaxRPM值。python中的标准方法是什么?

您可以将YAML输出传递给函数,函数根据您特定的搜索要求构造一个字典。您描述的行为听起来很特别,我不认为有任何内置的用法。

将您的
列表
转换为
dict
,其中包含键姓名:

result = {}
for elem in data['Vessels']:
    name = elem.pop('Name')
    result[name] = elem

data['Vessels'] = result

print(data['Tug'])
>> {'VesselType': 'EscortTug ...}

目前还没有一种标准的方法来实现这一点,这在很大程度上是因为YAML的键可能很复杂。这使得适用于JSON等更简单格式的路径匹配方法无法使用

如果您的YAML与您的YAML一样是“无标记”的,那么它仍然允许比JSON更复杂的结构,但您可以相当轻松地实现递归遍历YAML文件的集合类型(序列和映射),同时显式匹配索引、键和/或元素、值:

import ruamel.yaml as yaml

def _do_not_care():
    pass

def find_collection(d, key=_do_not_care, value=_do_not_care, results=None):

    def check_key_value(d, k, v, results):
        # print('checking', key, value, k, d[k], results)
        if k == key:
            if value in [_do_not_care, v]:
                results.append(d)
                return
        elif key == _do_not_care and v == value:
            results.append(d)
            return
        if isinstance(v, (dict, list)):
            find_collection(v, key, value, results)

    if results is None:
        results = []
    if isinstance(d, dict):
        for k in d:
            check_key_value(d, k, d[k], results)
    if isinstance(d, list):
        for k, v in enumerate(d):
            check_key_value(d, k, v, results)
    return results

def find_first(d, key=_do_not_care, value=_do_not_care):
    ret_val = find_collection(d, key, value)
    return ret_val[0] if ret_val else {}

def find_value_for_key(d, key):
    return find_first(d, key)[key]
通过上述步骤,您可以执行以下操作:

file_name = 'SampleCase.yml'
with open(file_name, 'r') as f:  
    data = yaml.safe_load(f)
for d in find_collection(data, value='Tug'):
    vessel_type = find_first(data, key='Name', value=d['VesselType'])
    port_propeller = find_first(vessel_type, key='Name', value='Port Propeller')
    print('Tug -> MaxRPM', find_value_for_key(port_propeller, key='MaxRPM'))
这将打印(假设输入已纠正,请参见第1点):


有几件事需要记住:

  • 您的YAML无效,因为指令和文档之间没有
    --
    分隔。它的前三行应该如下所示:

    %YAML 1.1
    ---
    VesselTypes:
    
    但是,可能根本没有必要指定该指令:七年后,PyYAML仍然不支持YAML 1.2,而且您的YAML似乎没有任何特定于YAML 1.1的内容

  • 您正在使用PyYAML的
    load()
    而没有
    Loader
    参数,如果您无法控制输入,则可能不安全。如果可以,您应该始终使用
    safe\u load
    (与源代码一样)


  • 以上内容是使用(支持YAML 1.2和1.1的PyYAML超集)进行测试的。免责声明:我是该软件包的作者)如果你必须坚持下去,我应该像PyYAML一样工作。

    如果它是一个常规任务,你可能会考虑把数据转换成JSON或XML,使用JSONPath或XPath。你能再看一下这个答案吗?它可能是我所看到的。YAML.Load(f)返回一个类似于字典的python对象。我只是不确定如何使用名称而不是idex来获取数据,我当然可以遍历数据并将其存储在单独的对象中,但我希望保留yaml.load(f)所加载的对象生成并传递给我的函数,并在必要时从中读取相关信息。
    Tug -> MaxRPM 1800
    
    %YAML 1.1
    ---
    VesselTypes: