我有一个嵌套的字典对象,我希望能够检索具有任意深度的键值。我可以通过继承dict
:
>>> class MyDict(dict):
... def recursive_get(self, *args, **kwargs):
... default = kwargs.get('default')
... cursor = self
... for a in args:
... if cursor is default: break
... cursor = cursor.get(a, default)
... return cursor
...
>>> d = MyDict(foo={'bar': 'baz'})
>>> d
{'foo': {'bar': 'baz'}}
>>> d.get('foo')
{'bar': 'baz'}
>>> d.recursive_get('foo')
{'bar': 'baz'}
>>> d.recursive_get('foo', 'bar')
'baz'
>>> d.recursive_get('bogus key', default='nonexistent key')
'nonexistent key'
但是,我不希望子类dict
来获取此行为。是否有一些具有等效或类似行为的内置方法?如果没有,是否有任何标准或外部模块提供此行为?
我目前正在使用Python 2.7,但我也很想知道3.x解决方案。
答案 0 :(得分:16)
执行此操作的一个非常常见的模式是使用空dict作为默认值:
d.get('foo', {}).get('bar')
如果您有多个密钥,则可以使用reduce
(请注意,必须在Python 3中reduce
导入from functools import reduce
)才能多次应用该操作
reduce(lambda c, k: c.get(k, {}), ['foo', 'bar'], d)
当然,您应该考虑将其包装到函数(或方法)中:
def recursive_get(d, *keys):
return reduce(lambda c, k: c.get(k, {}), keys, d)
答案 1 :(得分:5)
@ThomasOrozco's solution是正确的,但是求助于max_price
函数,只有在中间键 不存在的情况下,才需要避免tier_price
。如果这无关紧要,则可以直接使用tier_price
:
lambda
这是一个演示:
TypeError
如果您希望比使用dict.get
更明确,同时又避免使用from functools import reduce
def get_from_dict(dataDict, mapList):
"""Iterate nested dictionary"""
return reduce(dict.get, mapList, dataDict)
,则可以使用a = {'Alice': {'Car': {'Color': 'Blue'}}}
path = ['Alice', 'Car', 'Color']
get_from_dict(a, path) # 'Blue'
/ lambda
子句:
TypeError
最后,如果您希望在没有任何键的情况下提高try
,请使用except
或def get_from_dict(dataDict, mapList):
"""Iterate nested dictionary"""
try:
return reduce(dict.get, mapList, dataDict)
except TypeError:
return None # or some other default value
:
KeyError
请注意,operator.getitem
是dict.__getitem__
方法的语法糖。因此,这恰好与您通常如何访问字典值有关。 from functools import reduce
from operator import getitem
def getitem_from_dict(dataDict, mapList):
"""Iterate nested dictionary"""
return reduce(getitem, mapList, dataDict)
# or reduce(dict.__getitem__, mapList, dataDict)
模块只是提供了一种更具可读性的访问此方法的方法。
答案 2 :(得分:1)
没有我所知道的。但是,你根本不需要继承dict,你可以编写一个带字典,args和kwargs的函数并做同样的事情:
def recursive_get(d, *args, **kwargs):
default = kwargs.get('default')
cursor = d
for a in args:
if cursor is default: break
cursor = recursive_get(cursor, a, default)
return cursor
像这样使用
recursive_get(d, 'foo', 'bar')
答案 3 :(得分:1)
实际上,你可以在Python 3中实现这一点,因为它处理了默认的关键字参数和元组分解:
In [1]: def recursive_get(d, *args, default=None):
...: if not args:
...: return d
...: key, *args = args
...: return recursive_get(d.get(key, default), *args, default=default)
...:
类似的代码也可以在python 2中使用,但您需要恢复使用**kwargs
,就像您在示例中所做的那样。您还需要使用索引来分解*args
。
在任何情况下,如果你要使函数递归,就不需要循环。
您可以看到上面的代码演示了与现有方法相同的功能:
In [2]: d = {'foo': {'bar': 'baz'}}
In [3]: recursive_get(d, 'foo')
Out[3]: {'bar': 'baz'}
In [4]: recursive_get(d, 'foo', 'bar')
Out[4]: 'baz'
In [5]: recursive_get(d, 'bogus key', default='nonexistent key')
Out[5]: 'nonexistent key'
答案 4 :(得分:1)
您可以使用defaultdict为丢失的密钥提供空字典:
from collections import defaultdict
mydict = defaultdict(dict)
这只是一个深度 - mydict[missingkey]
是一个空的dict,mydict[missingkey][missing key]
是一个KeyError。您可以根据需要添加更多级别,方法是将其包含在更多defaultdict
s中,例如defaultdict(defaultdict(dict))
。你也可以将最里面的一个作为另一个defaultdict,为你的用例提供合理的工厂函数,例如
mydict = defaultdict(defaultdict(lambda: 'big summer blowout'))
如果你需要它去任意深度,你可以这样做:
def insanity():
return defaultdict(insanity)
print(insanity()[0][0][0][0])
答案 5 :(得分:-1)
collections.default_dict将至少处理为不存在的密钥提供默认值。
答案 6 :(得分:-1)
迭代解决方案
def deep_get(d:dict, keys, default=None, create=True):
if not keys:
return default
for key in keys[:-1]:
if key in d:
d = d[key]
elif create:
d[key] = {}
d = d[key]
else:
return default
key = keys[-1]
if key in d:
return d[key]
elif create:
d[key] = default
return default
def deep_set(d:dict, keys, value, create=True):
assert(keys)
for key in keys[:-1]:
if key in d:
d = d[key]
elif create:
d[key] = {}
d = d[key]
d[keys[-1]] = value
return value
我将在一个 Django 项目中测试它,例如:
keys = ('options', 'style', 'body', 'name')
val = deep_set(d, keys, deep_get(s, keys, 'dotted'))