是否有Python的dict.get()内置的递归版本?

时间:2015-01-29 22:07:42

标签: python dictionary recursion nested

我有一个嵌套的字典对象,我希望能够检索具有任意深度的键值。我可以通过继承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解决方案。

7 个答案:

答案 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,请使用exceptdef 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.getitemdict.__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'))