Python安全的方法来获取嵌套字典的值

例如,我有一个嵌套的字典。 只有一种方法可以使价值安全吗?

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

或者,也许python有像get()嵌套字典的方法?

你可以使用两次:

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

如果key1key2不存在,这将返回None

请注意,如果example_dict['key1']存在但是不是字典(或带有get方法的字典型对象),则仍可能引发AttributeError 。 如果example_dict['key1']try..except取代的,那么您发布的try..except代码会引发TypeError

另一个区别是, try...except短路后,第一个失踪的关键。 get电话的链不。


如果你想保留语法, example_dict['key1']['key2']但不希望它引发KeyErrors,那么你可以使用Hasher配方 :

 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'> 

请注意,这将返回一个空的哈希当一个键丢失。

由于Hasherdict一个子类,所以可以像使用dict使用Hasher。 所有相同的方法和语法都是可用的,Hashers只是以不同的方式处理丢失的键。

你可以像这样把一个正则dict转换成一个Hasher

 hasher = Hasher(example_dict) 

并把Hasher转换成一个正规的dict一样简单:

 regular_dict = dict(hasher) 

另一种select是把丑陋隐藏在一个辅助函数中:

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

所以你的代码的其余部分可以保持相对可读性:

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

你也可以使用python reduce :

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

build立在Yoav的答案,更安全的方法:

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

虽然减less的方法是简洁,但我认为一个简单的循环更容易打扰。 我还包括一个默认参数。

 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 

作为一个练习,了解如何减less单线工作,我做了以下工作。 但最终循环方法似乎更直观。

 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') 

一个简单的类,可以包装一个字典,并根据一个键检索:

 class FindKey(dict): def get(self, path, default=None): keys = path.split(".") val = None for key in keys: if val: if isinstance(val, list): val = [v.get(key, default) if v else None for v in val] else: val = val.get(key, default) else: val = dict.get(self, key, default) if not val: break return val 

例如:

 person = {'person':{'name':{'first':'John'}}} FindDict(person).get('person.name.first') # == 'John' 

如果密钥不存在,则默认返回None 。 您可以使用FindDict包装器中的default=键来覆盖它 – 例如`:

 FindDict(person, default='').get('person.name.last') # == doesn't exist, so '' 

看到这个深度获取属性后,我做了以下安全得到使用点符号嵌套dict值。 这适用于我,因为我的dicts是反序列化的MongoDB对象,所以我知道键名不包含. 秒。 此外,在我的上下文中,我可以指定一个虚假的后备值( None ),我没有在我的数据,所以我可以避免尝试/除了模式时调用函数。

 def deepgetitem(obj, item, fallback=None): """Steps through an item chain to get the ultimate value. If ultimate value or path to value does not exist, does not raise an exception and instead returns `fallback`. >>> d = {'snl_final': {'about': {'_icsd': {'icsd_id': 1}}}} >>> deepgetitem(d, 'snl_final.about._icsd.icsd_id') 1 >>> deepgetitem(d, 'snl_final.about._sandbox.sbx_id') >>> """ def getitem(obj, name): try: return obj[name] except (KeyError, TypeError): return None return reduce(getitem, item.split('.'), obj) 

对于第二级密钥检索,你可以这样做:

 key2_value = (example_dict.get('key1') or {}).get('key2') 

通过结合所有这些答案和我所做的小改动,我认为这个function是有用的。 其安全,快速,易于维护。

 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 >>>