我有一个嵌套的字典。是否只有一种安全地获取其中值的方法?
try:
example_dict['key1']['key2']
except KeyError:
pass
或者 Python 中是否有像 get()
方法一样可以访问嵌套字典的方法?
我有一个嵌套的字典。是否只有一种安全地获取其中值的方法?
try:
example_dict['key1']['key2']
except KeyError:
pass
或者 Python 中是否有像 get()
方法一样可以访问嵌套字典的方法?
你可以使用get
两次:
example_dict.get('key1', {}).get('key2')
如果key1
或key2
不存在,这将返回None
。
请注意,如果example_dict['key1']
存在但不是字典(或具有get
方法的类似字典的对象),则仍可能引发AttributeError
。如果example_dict['key1']
无法进行下标操作,则您发布的try...except
代码将引发TypeError
。
另一个区别是,try...except
在第一个丢失的键后立即短路。一系列的get
调用则没有。
如果您希望保留语法example_dict['key1']['key2']
,但不希望它引发KeyErrors,那么您可以使用Hasher recipe:
class Hasher(dict):
# https://dev59.com/WXA75IYBdhLWcg3wPmZ8#3405143
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'>
请注意,当键值不存在时,这将返回一个空的Hasher。
由于Hasher
是dict
的子类,您可以像使用dict
一样使用Hasher
。所有相同的方法和语法都可用,只是Hasher对待缺失的键有所不同。
您可以像这样将常规的dict
转换为Hasher
:
hasher = Hasher(example_dict)
并且可以轻松地将Hasher
转换为常规的dict
:
regular_dict = dict(hasher)
另一种选择是将这个不美观的部分隐藏在一个辅助函数中:
def safeget(dct, *keys):
for key in keys:
try:
dct = dct[key]
except KeyError:
return None
return dct
所以你的其余代码可以保持相对易读:
safeget(example_dict, 'key1', 'key2')
safeget
方法在很多方面并不安全,因为它会覆盖原始的字典,这意味着您无法安全地执行诸如 safeget(dct, 'a', 'b') 或 safeget(dct, 'a')
这样的操作。 - neverfoxdct = dct[key]
将一个新值重新赋给了局部变量 dct
。这不会改变原始字典(因此原始字典不受 safeget
的影响)。如果使用 dct[key] = ...
,则会修改原始字典。换句话说,在Python中,名称绑定到值。将新值分配给名称不会影响旧值(除非旧值没有更多的引用,在这种情况下(在CPython中)它将被垃圾回收)。 - unutbusafeget
方法在嵌套字典中的键存在但值为 null 时也会失败。下一次迭代会抛出 TypeError: 'NoneType' object is not subscriptable
。 - Stanley F.with suppress(KeyError):
。请参见此答案:https://dev59.com/aG7Xa4cB1Zd3GeqPuNJ9#45874251 - IODEV通过结合这里提供的所有答案和我所做的一些小改变,我认为这个函数会很有用。它是安全的、快速的、易于维护的。
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
deep_get({'a': 1}, "a.b")
返回 None
,但我期望会抛出 KeyError
或其他异常。 - Saddle Pointdeep_get(person, 'lastNames.0.name')
。 - blisstdev你也可以使用Python的reduce函数:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)
example_dict.get('key1',{}).get('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)
我建议你尝试使用python-benedict
。
它是一个dict
子类,提供了 keypath 支持和更多功能。
安装方法:pip install python-benedict
from benedict import benedict
example_dict = benedict(example_dict, keypath_separator='.')
现在你可以使用键路径访问嵌套值:
val = example_dict['key1.key2']
# using 'get' method to avoid a possible KeyError:
val = example_dict.get('key1.key2')
或者使用键列表访问嵌套值:
val = example_dict['key1', 'key2']
# using get to avoid a possible KeyError:
val = example_dict.get(['key1', 'key2'])
这个项目已经在GitHub上得到了良好的测试并且是开源的:
https://github.com/fabiocaccamo/python-benedict
注意:我是这个项目的作者。
d.get('a.b[0].c[-1]')
。 - Fabio Caccamoglom
是一个很棒的库,可以进行点查询:
In [1]: from glom import glom
In [2]: data = {'a': {'b': {'c': 'd'}}}
In [3]: glom(data, "a.b.c")
Out[3]: 'd'
In [4]: glom(data, "a.b.foo")
---------------------------------------------------------------------------
PathAccessError Traceback (most recent call last)
<ipython-input-4-2a3467493ac4> in <module>
----> 1 glom(data, "a.b.foo")
~/.cache/pypoetry/virtualenvs/neural-knapsack-dE7ihQtM-py3.8/lib/python3.8/site-packages/glom/core.py in glom(target, spec, **kwargs)
2179
2180 if err:
-> 2181 raise err
2182 return ret
2183
PathAccessError: error raised while processing, details below.
Target-spec trace (most recent last):
- Target: {'a': {'b': {'c': 'd'}}}
- Spec: 'a.b.foo'
glom.core.PathAccessError: could not access 'foo', part 2 of Path('a', 'b', 'foo'), got error: KeyError('foo')
使用 default
保护:
In [5]: glom(data, "a.b.foo", default="spam")
Out[5]: 'spam'
glom
的优美之处在于其灵活的规范参数。例如,可以轻松从以下 data
中提取所有的名字:In [8]: data = {
...: "people": [
...: {"first_name": "Alice", "last_name": "Adams"},
...: {"first_name": "Bob", "last_name": "Barker"}
...: ]
...: }
In [9]: glom(data, ("people", ["first_name"]))
Out[9]: ['Alice', 'Bob']
阅读glom
文档以获取更多示例。
您可以使用 pydash 库:
import pydash as _ #NOTE require `pip install pydash`
_.get(example_dict, 'key1.key2', default='Default')
虽然 reduce 方法简洁明了,但我认为使用循环更容易理解。我还包含了一个默认参数。
def deep_get(_dict, keys, default=None):
for key in keys:
if isinstance(_dict, dict):
_dict = _dict.get(key, default)
else:
return default
return _dict
作为理解如何使用一行reduce的练习,我做了以下操作。但最终循环方法对我来说似乎更直观。
def deep_get(_dict, keys, default=None):
def _reducer(d, key):
if isinstance(d, dict):
return d.get(key, default)
return default
return reduce(_reducer, keys, _dict)
使用方法
nested = {'a': {'b': {'c': 42}}}
print deep_get(nested, ['a', 'b'])
print deep_get(nested, ['a', 'b', 'z', 'z'], default='missing')
except KeyError:
子句中指定默认值。 - Peter Schornexample_dict = {'key1': None}
(会导致TypeError
错误,或者如果使用了get('key1', {})
,则会导致AttributeError
错误)。请参考这个答案了解更多信息。 - undefined