【问题标题】:Parsing yaml file in python在python中解析yaml文件
【发布时间】:2018-02-16 13:26:47
【问题描述】:

我有一个 YAML 文件,如下所示:

categories:
    - Exp:
        topics:
            - Experience:
                keywords:
                    - refresh
                    - tapping
            - Availability:
                keywords:
                    - fish
                    - get work
    - Wp:
        topics:
            - Waits:
                keywords:
                    - wait
                    - long line
            - Process:
                keywords:
                    - check in
                    - check-in

我想知道是否有更好/更简单的方法来解析 python 中的 yaml 文件。我基本上想在 yaml 文件中的字符串中搜索 topics 中的关键字,如果该主题的一个关键字存在,则打印该主题的名称以及 category's 名称。并为每个主题执行此操作。从上面的yaml文件示例来看,分类为ExpWp,其下的主题分别为ExperienceAvailability&WaitsProcess

我要测试的字符串是:

s = "Refresh after tapping takes long wait. Can't get work"

所以输出应该是:

topic: Experience
topic: Availability
category: Exp
topic: Waits
category: Wp 

我正在尝试按如下方式解析 yaml:

import yaml

with open("file.yaml", 'r') as stream:
    try:
        yml = yaml.load(stream)
    except yaml.YAMLError as exc:
        print(exc)

for key, val in yml.items():
    for i in val:
        for k, v in i.items():
            for k1, v1 in v.items():
                if k1 == 'topics':
                    for j in v1:
                        for k2, v2 in j.items():
                            for k3, v3 in v2.items():
                                if k3 == 'keywords':
                                    print v3

但是,正如您所看到的,有很多 for 循环可以到达主题关键字,这是一段非常糟糕的代码。如何以更好的方式做到这一点?

【问题讨论】:

  • yaml.load() 返回时,您已经解析了 YAML 文档。到目前为止,这是代码中最糟糕的部分,而不是 for 循环。如果您无法控制输入,则使用 yaml.load() 是不安全的。您很少需要不安全的解析,当然不是您的,并且使用它可能会丢失您的光盘内容或更糟。

标签: python string dictionary yaml string-matching


【解决方案1】:

你也可以这样做。

yaml 返回的结构有些复杂;但是,它可以看作是字典和列表的集合。通过明智地选择列表的元素编号和字典的元素名称,可以从四个分支中的每一个中选择“关键字”元素。然后,在一个 for 循环中,这些选项组合中的每一个都用于挑选出关键字列表中的一个。字典元素名称的选择证明是您要与关键字关联的项目!

在最后一步中,我们查看我们创建的字典的键,看看哪些项目出现在给定的句子中,并从字典中返回这些键的值。

>>> import yaml
>>> yml = yaml.load(open('file.yaml'))
>>> lookups = {}
>>> for item_5, item_1, item_3, item_2 in [[0, 'Exp', 0, 'Experience'], [0, 'Exp', 1, 'Availability'], [1, 'Wp', 0, 'Waits'], [1, 'Wp', 1, 'Process']]:
...     keywords = yml['categories'][item_5][item_1]['topics'][item_3][item_2]['keywords']
...     for word in keywords:
...         lookups[word] = {'category': item_1, 'topic': item_2}
... 
>>> lookups
{'check-in': {'category': 'Wp', 'topic': 'Process'}, 'long line': {'category': 'Wp', 'topic': 'Waits'}, 'get work': {'category': 'Exp', 'topic': 'Availability'}, 'tapping': {'category': 'Exp', 'topic': 'Experience'}, 'check in': {'category': 'Wp', 'topic': 'Process'}, 'fish': {'category': 'Exp', 'topic': 'Availability'}, 'refresh': {'category': 'Exp', 'topic': 'Experience'}, 'wait': {'category': 'Wp', 'topic': 'Waits'}}
>>> s = "Refresh after tapping takes long wait. Can't get work".lower()

>>> for word in lookups:
...     if word in s:
...         word, lookups[word]
...         
('get work', {'category': 'Exp', 'topic': 'Availability'})
('tapping', {'category': 'Exp', 'topic': 'Experience'})
('refresh', {'category': 'Exp', 'topic': 'Experience'})
('wait', {'category': 'Wp', 'topic': 'Waits'})

【讨论】:

    【解决方案2】:

    你可以这样做:

    def walk(root, kw, path=''):
        found = []
    
        if isinstance(root, list):
            for i, item in enumerate(root):
                found.extend(walk(item, kw, path='%s/%d' % (path, i)))
        elif isinstance(root, dict):
            for k, v in root.items():
                if k == 'keywords':
                    # this produces a flat list of all the keywords
                    # in a topic.
                    words = sum((x.split() for x in v), [])
    
                    # see if the list of target keywords contains
                    # any of the topic keywords
                    if any(x in kw for x in words):
                        return [path]
                found.extend(walk(v, kw, path='%s/%s' % (path, k)))
    
        return found
    

    鉴于:

    phrase = "Refresh after tapping takes long wait. Can't get work"
    kw = phrase.lower().split()
    

    如果我们这样调用walk

    found = walk(data, kw)
    

    我们得到结果:

    ['/categories/0/Exp/topics/0/Experience', 
     '/categories/0/Exp/topics/1/Availability', 
     '/categories/1/Wp/topics/0/Waits']
    

    您可以很容易地从这些路径中提取类别或主题:

    for path in found:
        cat = path.split('/')[3]
        topic = path.split('/')[6]
        print 'category:', cat
        print 'topic:', topic
    

    这让我很感动:

    category: Exp
    topic: Experience
    category: Exp
    topic: Availability
    category: Wp
    topic: Waits
    

    您显然可以根据需要对该列表进行唯一化。

    这些路径也恰好是有效的jsonpointer 路径,这意味着您可以使用jsonpointer.resolve_pointer 做一些聪明的事情:

    >>> jsonpointer.resolve_pointer(data, found[0])
    {'keywords': ['refresh', 'tapping']}
    

    【讨论】:

    • this cat = path.split('/')[3] topic = path.split('/')[6] 不能硬编码吗?因为我有一个需要检查的短语文件,有些可能匹配更多主题,有些可能匹配较少。
    • 没关系,你会得到多个路径。在任何情况下,类别始终是路径的第三个组成部分,主题名称也是如此。尝试给它输入不同的样本数据,看看会发生什么。
    • 我做了,但我得到一个错误 topic = path.split('/')[6] IndexError: list index out of range
    • 您能否更新问题中的示例数据?我很乐意再看一遍。如果我不知道输入数据是什么样的,就很难验证。
    • 有这样一种情况:'/categories/0/Exp' 即没有主题的地方
    猜你喜欢
    • 2010-12-18
    • 2021-01-28
    • 1970-01-01
    • 2021-09-28
    • 2014-11-05
    • 1970-01-01
    相关资源
    最近更新 更多