Python 是否有内置dict.get()的递归版本?
我有一个嵌套的dictionary对象,我希望能够检索任意深度的键的值。我可以通过子类化Python 是否有内置dict.get()的递归版本?,python,dictionary,recursion,nested,Python,Dictionary,Recursion,Nested,我有一个嵌套的dictionary对象,我希望能够检索任意深度的键的值。我可以通过子类化dict来实现这一点: >>> class MyDict(dict): ... def recursive_get(self, *args, **kwargs): ... default = kwargs.get('default') ... cursor = self ... for a in args: ...
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
。是否有一些内置方法具有相同或类似的行为?如果没有,是否有任何标准或外部模块提供这种行为
目前我正在使用Python2.7,不过我也很想听听3.x解决方案。我还不知道。但是,您根本不需要将dict子类化,只需编写一个函数,该函数接受dictionary、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')
将至少为不存在的键处理默认值的提供。执行此操作的一种常见模式是使用空dict作为默认值:
d.get('foo', {}).get('bar')
如果有多个键,可以使用reduce
(注意,在Python 3中,必须导入reduce
:from functools import reduce
)多次应用该操作
reduce(lambda c, k: c.get(k, {}), ['foo', 'bar'], d)
当然,你应该考虑把这个打包成一个函数(或方法):
考虑到Python 3对默认关键字参数和元组分解的处理,您实际上可以在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'
您可以使用defaultdict为缺少的密钥提供空dict:
from collections import defaultdict
mydict = defaultdict(dict)
这只会深入一层-mydict[missingkey]
是一个空的dict,mydict[missingkey][missingkey]
是一个键错误。您可以根据需要添加任意多个级别,方法是将其包装成更多的defaultdict
s,例如defaultdict(defaultdict(dict))
。您还可以将最里面的一个作为另一个defaultdict,并为您的用例提供一个合理的工厂函数,例如
mydict = defaultdict(defaultdict(lambda: 'big summer blowout'))
如果需要它进入任意深度,可以这样做:
def insanity():
return defaultdict(insanity)
print(insanity()[0][0][0][0])
是正确的,但使用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
/中使用以下子句:
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.。\uuuuu getitem\uuuuu
:
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)
请注意,[]
是\uuu getitem\uu
方法的语法糖。因此,这正是您通常访问字典值的方式。操作符
模块只是提供了一种更可读的方法来访问此方法。迭代解决方案
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'))
d、 get('foo')。get('bar')?听起来您对使用问题中发布的代码实现的功能相当满意。是否有任何特殊原因使您不想子类化dict
?@Foon,它不会嵌套到任意深度,并且会引发异常(而不是返回默认值)如果链中早期的某个键不存在。@JohnY-有几个原因-我希望有一些方法可以在dict对象上执行此操作,而不必将它们强制转换为MyDict对象,我很好奇,如果不将dict子类化,这是否可行。否则,子类化工作得很好。我想不出一种方法使它递归。dict.get()
也是如此。这不是我关心的行为。@Jayhandren看到我的答案了。我已经调试了这些函数,现在它们已用于生产。答案在这里:谢谢!我想知道是否有一种Python-idomatic的方式来实现这一点;使用空的DITCS作为默认值为<代码> GET()/<代码>,使用匿名函数看起来都是很好的习惯用法。尽管这回答了OP的问题,但我认为JPP的答案更简洁。在某些情况下,提出一个KeyError
比返回一个空的dict更自然。此外,jpp的答案更通用,因为它可以用于嵌套字典、嵌套列表以及两者的混合。请注意,这也适用于嵌套列表。如果任何索引超出范围,则使用getitem
的变量将引发一个indexer
。更好的是,建议的答案可用于同时包含嵌套列表和嵌套dict的dict,这在处理json数据时非常有用……我忘了提到元组和任何实现\uuu getitem\uuu
方法的对象。。。
keys = ('options', 'style', 'body', 'name')
val = deep_set(d, keys, deep_get(s, keys, 'dotted'))