Python:使用列表安全地访问字典?

Python: Safe dictionary access with lists?

是否有一种无异常的方式来访问包含列表的字典中的值。例如,如果我有:

data = {
    "object_1": {
        "object_2": {
            "list": [
                {
                    "property": "hello"
                }
            ]
        }
    }
}

如何安全地访问路径 data['object_1']['object_2']['list'][0]['property'](即 return 如果无法在不抛出错误的情况下访问某些默认值)?我试图避免将它们包装在 try-except 中。我看过基于 reduce 的方法,但它没有考虑到字典中的列表。

在 JS 中,我可以这样写:

data.object_1?.object_2?.list[0]?.property ?? 'nothing_found'

Python中有类似的东西吗?

对于dict你可以使用get方法。对于列表,您可以注意索引:

data.get('object_1', {}).get('object_2', {}).get('list', [{}])[0].get('property', default)

这有点尴尬,因为它会为每次调用创建一个新的临时命令或丢失 get。对于没有等效方法的列表也不是超级安全。

您也可以将 getter 包装在一个小例程中以支持列表,但这并不值得。您最好编写一个 one-off 实用函数,该函数使用异常处理或初步检查来处理您想要响应的情况:

def get(obj, *keys, default=None):
    for key in keys:
        try:
            obj = obj[key]
        except KeyError, IndexError:
            return default
    return obj

异常处理与其他方式相比有几个巨大的优势。一方面,您不必根据对象是字典还是列表对键进行单独检查。另一方面,您可以支持几乎任何其他支持 __getitem__ 索引的合理类型。为了表明我的意思,这里是请求许可而不是宽恕的方法:

from collections.abc import Mapping, Sequence
from operator import index

def get(obj, *keys, default=None):
    for key in keys:
        if isinstance(obj, Mapping):
            if key not in obj:
                return default
        elif isinstance(obj, Sequence):
            try:
                idx = index(key)
            except TypeError:
                return default
            if len(obj) <= idx or len(obj) < -idx:
                 return default
        obj = obj[key]
    return obj

观察检查是多么尴尬和 error-prone。尝试传入一个自定义对象而不是 list,或者一个不是整数的键。在 Python 中,小心使用异常是你的朋友,请求宽恕而不是许可是 pythonic 的原因是有原因的。

呃。是的,访问这样的 JSON 数据结构真是太糟糕了, 有点尴尬。

Glom 救援!

有两种获胜方式:

  1. 您可以只指定 ... , default=None) 以避免异常,..或..

  2. 使用Coalesce

     print(glom(data, {'object_1.object_2.list': ['property']}, default=None))
    

有一种方法可以做到这一点,但它会涉及 get 方法,并且会涉及大量检查或使用临时值。

一个示例查找函数如下所示:

def lookup(data):
    object_1 = data.get("object_1")
    if object_1 is None:
        # return your default
    object_2 = object_1.get('object_2')
    # and so on...

在 Python 3.10 及更高版本中,还有结构模式匹配可以提供帮助,在这种情况下,您可以这样做:

match data:
    case {'object_1': {'object_2': {'list': [{'property': x}]}}}:
        print(x) # should print 'hello'
    case _:
        print(<your_default>)

请记住,这仅适用于最新版本的 Python(Python.org 上的在线 Python 控制台仍仅适用于 Python3.9,代码以上会导致语法错误)。

在下面的代码中,如果 'object_1'/'object_2'/'list' 键不存在,x 将 return None。

此外,如果我们能够访问 'list' 键,那么我们的 x 不是 None 并且我们应该确保列表的长度应该大于零,然后我们可以搜索对于 'property' 键。

x = data.get('object_1', {}).get('object_2', {}).get('list')
    
if x is not None and len(x) > 0:
        print(x[0].get('property'))
else:
        print(None)