Python安全的方法来获取嵌套字典的值
例如,我有一个嵌套的字典。 只有一种方法可以使价值安全吗?
try: example_dict['key1']['key2'] except KeyError: pass
或者,也许python有像get()
嵌套字典的方法?
你可以使用两次:
example_dict.get('key1', {}).get('key2')
如果key1
或key2
不存在,这将返回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'>
请注意,这将返回一个空的哈希当一个键丢失。
由于Hasher
是dict
一个子类,所以可以像使用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 >>>