我有一本嵌套的字典。只有一种方法可以安全地获取价值吗?
try:
example_dict['key1']['key2']
except KeyError:
pass
也许python有像get()
嵌套字典这样的方法?
except keyerror:
子句中指定默认值。
我有一本嵌套的字典。只有一种方法可以安全地获取价值吗?
try:
example_dict['key1']['key2']
except KeyError:
pass
也许python有像get()
嵌套字典这样的方法?
except keyerror:
子句中指定默认值。
Answers:
您可以使用get
两次:
example_dict.get('key1', {}).get('key2')
None
如果存在key1
或key2
不存在,它将返回。
请注意,这仍可能引发AttributeError
if example_dict['key1']
存在但不是dict(或带有get
方法的类似dict的对象)。try..except
如果发布的代码无法订阅,则会引发一个TypeError
代替example_dict['key1']
。
另一个区别是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
您可以像使用一样使用Hasher dict
。可以使用所有相同的方法和语法,而Hashers只是以不同方式对待丢失的密钥。
您可以将常规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') or safeget(dct, 'a')
。
safeget
永远不会覆盖原始词典。它将返回原始字典,原始字典中的值,或者None
。
dct = dct[key]
将新值重新分配给局部变量 dct
。这不会改变原始字典(因此原始字典不受的影响safeget
)。另一方面,dct[key] = ...
如果使用了原始字典,则原始字典将被修改。换句话说,在Python中,名称绑定到values。将新值分配给名称不会影响旧值(除非不再有对旧值的引用,在这种情况下(在CPython中)它将被垃圾回收。)
您还可以使用python reduce:
def deep_get(dictionary, *keys):
return reduce(lambda d, key: d.get(key) if d else None, keys, dictionary)
通过将此处所有这些答案与我所做的微小更改结合起来,我认为此功能将很有用。其安全,快速,易于维护。
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
或其他。
None
为Raise KeyError
以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)
虽然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
为了了解还原型单缸衬套的工作原理,我做了以下工作。但最终循环方法对我来说似乎更直观。
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')
我建议你试试python-benedict
。
它是一个dict
子类,提供键路径支持等等。
安装: pip install python-benedict
from benedict import benedict
example_dict = benedict(example_dict, keypath_separator='.')
现在您可以使用keypath访问嵌套值:
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上经过了良好的测试和开源:
d.get('a.b[0].c[-1]')
一个简单的类,可以包装字典并根据键进行检索:
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
默认情况下返回。您可以使用包装器中的default=
键覆盖它FindDict
,例如`:
FindDict(person, default='').get('person.name.last') # == doesn't exist, so ''
看到此属性后,我进行了以下操作以dict
使用点表示法安全地获取嵌套值。这对我dicts
有用,因为我是反序列化的MongoDB对象,所以我知道键名不包含.
。另外,在我的上下文中,我可以指定一个None
我的数据中没有的虚假后备值(),因此在调用该函数时可以避免使用try / except模式。
from functools import reduce # Python 3
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 fallback
return reduce(getitem, item.split('.'), obj)
fallback
实际上并未在函数中使用。
.
sep=','
关键字arg来概括给定(sep,fallback)条件。@denvar,如果在reduce序列之后obj
说类型int
,那么obj [name]引发TypeError,我抓住了。如果我改用obj.get(name)或obj.get(name,fallback),它将引发AttributeError,因此无论哪种方式我都需要捕获。
同一件事的另一个函数也返回一个布尔值,表示是否找到了密钥,并处理一些意外错误。
'''
json : json to extract value from if exists
path : details.detail.first_name
empty path represents root
returns a tuple (boolean, object)
boolean : True if path exists, otherwise False
object : the object if path exists otherwise None
'''
def get_json_value_at_path(json, path=None, default=None):
if not bool(path):
return True, json
if type(json) is not dict :
raise ValueError(f'json={json}, path={path} not supported, json must be a dict')
if type(path) is not str and type(path) is not list:
raise ValueError(f'path format {path} not supported, path can be a list of strings like [x,y,z] or a string like x.y.z')
if type(path) is str:
path = path.strip('.').split('.')
key = path[0]
if key in json.keys():
return get_json_value_at_path(json[key], path[1:], default)
else:
return False, default
用法示例:
my_json = {'details' : {'first_name' : 'holla', 'last_name' : 'holla'}}
print(get_json_value_at_path(my_json, 'details.first_name', ''))
print(get_json_value_at_path(my_json, 'details.phone', ''))
(真的,“ holla”)
(错误,“”)
您可以使用pydash:
import pydash as _
_.get(example_dict, 'key1.key2', default='Default')
对于嵌套字典/ JSON查找,可以使用dictor
点安装独裁者
字典对象
{
"characters": {
"Lonestar": {
"id": 55923,
"role": "renegade",
"items": [
"space winnebago",
"leather jacket"
]
},
"Barfolomew": {
"id": 55924,
"role": "mawg",
"items": [
"peanut butter jar",
"waggy tail"
]
},
"Dark Helmet": {
"id": 99999,
"role": "Good is dumb",
"items": [
"Shwartz",
"helmet"
]
},
"Skroob": {
"id": 12345,
"role": "Spaceballs CEO",
"items": [
"luggage"
]
}
}
}
要获得Lonestar的商品,只需提供一个点分隔的路径,即
import json
from dictor import dictor
with open('test.json') as data:
data = json.load(data)
print dictor(data, 'characters.Lonestar.items')
>> [u'space winnebago', u'leather jacket']
您可以提供备用值,以防路径中的键不存在
您还有更多选择,例如忽略字母大写和使用''以外的其他字符。作为路径分隔符
我几乎没有改变这个答案。我添加了检查是否正在使用带有数字的列表。所以现在我们可以使用任何一种方式。deep_get(allTemp, [0], {})
或deep_get(getMinimalTemp, [0, minimalTemperatureKey], 26)
等
def deep_get(_dict, keys, default=None):
def _reducer(d, key):
if isinstance(d, dict):
return d.get(key, default)
if isinstance(d, list):
return d[key] if len(d) > 0 else default
return default
return reduce(_reducer, keys, _dict)
已经有了很多好的答案,但是我想出了一个名为get的函数,类似于JavaScript领域中的lodash get,它还支持按索引进入列表:
def get(value, keys, default_value = None):
'''
Useful for reaching into nested JSON like data
Inspired by JavaScript lodash get and Clojure get-in etc.
'''
if value is None or keys is None:
return None
path = keys.split('.') if isinstance(keys, str) else keys
result = value
def valid_index(key):
return re.match('^([1-9][0-9]*|[0-9])$', key) and int(key) >= 0
def is_dict_like(v):
return hasattr(v, '__getitem__') and hasattr(v, '__contains__')
for key in path:
if isinstance(result, list) and valid_index(key) and int(key) < len(result):
result = result[int(key)] if int(key) < len(result) else None
elif is_dict_like(result) and key in result:
result = result[key]
else:
result = default_value
break
return result
def test_get():
assert get(None, ['foo']) == None
assert get({'foo': 1}, None) == None
assert get(None, None) == None
assert get({'foo': 1}, []) == {'foo': 1}
assert get({'foo': 1}, ['foo']) == 1
assert get({'foo': 1}, ['bar']) == None
assert get({'foo': 1}, ['bar'], 'the default') == 'the default'
assert get({'foo': {'bar': 'hello'}}, ['foo', 'bar']) == 'hello'
assert get({'foo': {'bar': 'hello'}}, 'foo.bar') == 'hello'
assert get({'foo': [{'bar': 'hello'}]}, 'foo.0.bar') == 'hello'
assert get({'foo': [{'bar': 'hello'}]}, 'foo.1') == None
assert get({'foo': [{'bar': 'hello'}]}, 'foo.1.bar') == None
assert get(['foo', 'bar'], '1') == 'bar'
assert get(['foo', 'bar'], '2') == None