我有一本嵌套的字典。是否只有一种方法可以安全地传递价值观?

try:
    example_dict['key1']['key2']
except KeyError:
    pass

或者python有一个类似get()的方法用于嵌套字典?


当前回答

你可以使用开源ndicts包中的NestedDict(我是作者),它有一个完全像字典一样的安全get方法。

>>> from ndicts import NestedDict
>>> nd = NestedDict({"key1": {"key2": 0}}
>>> nd.get(("key1", "key2))
0
>>> nd.get("asd")

其他回答

通过把所有这些答案和我做的小改变结合起来,我认为这个函数会很有用。安全、快捷、易于维护。

def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

例子:

from functools import reduce
def deep_get(dictionary, keys, default=None):
    return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)

person = {'person':{'name':{'first':'John'}}}
print(deep_get(person, "person.name.first"))    # John

print(deep_get(person, "person.name.lastname")) # None

print(deep_get(person, "person.name.lastname", default="No lastname"))  # No lastname

我使用的一个解决方案类似于double get,但具有使用if else逻辑避免TypeError的额外能力:

    value = example_dict['key1']['key2'] if example_dict.get('key1') and example_dict['key1'].get('key2') else default_value

然而,字典嵌套越多,这就变得越麻烦。

你可以使用get两次:

example_dict.get('key1', {}).get('key2')

如果key1或key2不存在,则返回None。

注意,如果example_dict['key1']存在但不是dict(或具有get方法的类dict对象),仍然可能引发AttributeError。如果example_dict['key1']不可下标,你发布的try..except代码将引发TypeError。

另一个区别是try…除非在第一次丢失钥匙后立即发生短路。get调用链则不然。


如果您希望保留语法example_dict['key1']['key2'],但不希望它引发KeyErrors,那么您可以使用哈希recipe:

class Hasher(dict):
    # https://stackoverflow.com/a/3405143/190597
    def __missing__(self, key):
        value = self[key] = type(self)()
        return value

example_dict = Hasher()
print(example_dict['key1'])
# {}
print(example_dict['key1']['key2'])
# {}
print(type(example_dict['key1']['key2']))
# <class '__main__.Hasher'>

注意,当缺少一个键时,返回一个空的hash。

因为Hasher是dict的一个子类,你可以像使用dict一样使用Hasher。所有相同的方法和语法都是可用的,哈希器只是以不同的方式对待缺失的键。

你可以像这样把一个普通字典转换成哈希:

hasher = Hasher(example_dict)

并将哈希转换为普通字典一样容易:

regular_dict = dict(hasher)

另一种选择是在helper函数中隐藏丑陋的代码:

def safeget(dct, *keys):
    for key in keys:
        try:
            dct = dct[key]
        except KeyError:
            return None
    return dct

这样你剩下的代码就可以保持相对的可读性:

safeget(example_dict, 'key1', 'key2')

根据Yoav的回答,一个更安全的方法是:

def deep_get(dictionary, *keys):
    return reduce(lambda d, key: d.get(key, None) if isinstance(d, dict) else None, keys, dictionary)

递归解。它不是最有效的,但我发现它比其他示例更具可读性,而且它不依赖于functools。

def deep_get(d, keys):
    if not keys or d is None:
        return d
    return deep_get(d.get(keys[0]), keys[1:])

例子

d = {'meta': {'status': 'OK', 'status_code': 200}}
deep_get(d, ['meta', 'status_code'])     # => 200
deep_get(d, ['garbage', 'status_code'])  # => None

一个更精致的版本

def deep_get(d, keys, default=None):
    """
    Example:
        d = {'meta': {'status': 'OK', 'status_code': 200}}
        deep_get(d, ['meta', 'status_code'])          # => 200
        deep_get(d, ['garbage', 'status_code'])       # => None
        deep_get(d, ['meta', 'garbage'], default='-') # => '-'
    """
    assert type(keys) is list
    if d is None:
        return default
    if not keys:
        return d
    return deep_get(d.get(keys[0]), keys[1:], default)