【问题标题】:Is there a recursive version of the dict.get() built-in?是否有 dict.get() 内置的递归版本?
【发布时间】:2026-01-27 15:45:02
【问题描述】:

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

【问题讨论】:

  • d.get('foo').get('bar') ?
  • 听起来您对使用问题中发布的代码实现的功能相当满意。你有什么特别的原因不想继承dict吗?
  • @Foon,它不会嵌套到任意深度,如果链中早期的某个键不存在,它将引发异常(而不是返回默认值)。
  • @JohnY - 只是几个原因 - 我希望有一些方法可以在 dict 对象上执行此操作而不将它们强制转换为 MyDict 对象,我很好奇如果没有子类化 dict 这是否可能。否则,子类化就可以了。

标签: python dictionary recursion nested


【解决方案1】:

执行此操作的一个非常常见的模式是使用空字典作为默认值:

d.get('foo', {}).get('bar')

如果您有多个键,您可以使用reduce(注意在 Python 3 中必须导入 reducefrom 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)

【讨论】:

  • 谢谢!我想知道是否有一种 Python 惯用的方式来做到这一点;使用空字典作为get() 的默认值并使用匿名函数看起来都不错。
  • 尽管这回答了 OP 的问题,但我认为 jpp 的回答更简洁。在某些情况下提出KeyError 比返回空字典更自然。此外,jpp 的答案更通用,因为它可以用于嵌套字典、嵌套列表以及两者的混合。
  • 这涵盖了大多数情况,但缺点是if d.get(k) is None 的常规测试不再起作用,因为此实现无法区分指向空字典的键与不能被发现
【解决方案2】:

@ThomasOrozco's solution 是正确的,但使用了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.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)

注意[]__getitem__ 方法的语法糖。因此,这与您通常如何访问字典值密切相关。 operator 模块只是提供了一种更易读的方法来访问这个方法。

【讨论】:

  • 请注意,这也适用于嵌套列表。如果任何索引超出范围,使用getitem 的变体将引发IndexError
  • 更好的是,建议的答案可用于包含嵌套列表和嵌套字典的字典,这在处理 json 数据时很有用。
  • ...我忘了提及元组和任何实现__getitem__ 方法的对象...
【解决方案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'

【讨论】:

    【解决方案4】:

    您可以使用 defaultdict 为您提供缺少键的空 dict:

    from collections import defaultdict
    mydict = defaultdict(dict)
    

    这只深入一层 - mydict[missingkey] 是一个空字典,mydict[missingkey][missing key] 是一个 KeyError。您可以通过将其包裹在更多defaultdicts 中来添加任意数量的级别,例如defaultdict(defaultdict(dict))。您还可以将最里面的一个作为另一个 defaultdict,并为您的用例提供合理的工厂功能,例如

    mydict = defaultdict(defaultdict(lambda: 'big summer blowout'))
    

    如果你需要它去任意深度,你可以这样做:

    def insanity():
        return defaultdict(insanity)
    
    print(insanity()[0][0][0][0])
    

    【讨论】:

      【解决方案5】:

      我不知道。但是,您根本不需要子类化 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')
      

      【讨论】:

        【解决方案6】:

        collections.default_dict 至少会为不存在的键提供默认值。

        【讨论】:

        • 我想不出一种让它递归的方法。
        • dict.get() 也是如此。这不是我在这里关心的行为。
        • @jayhendren 看到我的回答。我已经调试了这些功能,它们现在已用于生产。答案在这里:*.com/a/65842260/7076615
        【解决方案7】:

        迭代解决方案

        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'))
        

        【讨论】: