¿Hay una versión recursiva del dict.get () de Python integrado?

Tengo un objeto de diccionario nested y quiero poder recuperar valores de claves con una profundidad arbitraria. Soy capaz de hacer esto por subclasificación de 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' 

Sin embargo, no quiero tener que hacer una subclase de dict para obtener este comportamiento. ¿Hay algún método incorporado que tenga un comportamiento equivalente o similar? Si no es así, ¿hay algún módulo estándar o externo que proporcione este comportamiento?

Estoy usando Python 2.7 en este momento, aunque me gustaría saber sobre las soluciones 3.x también.

Un patrón muy común para hacer esto es usar un dict vacío como su predeterminado:

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

Si tiene más de un par de claves, podría usar reduce (tenga en cuenta que en Python 3, la reduce debe importarse: from functools import reduce ) para aplicar la operación varias veces

 reduce(lambda c, k: c.get(k, {}), ['foo', 'bar'], d) 

Por supuesto, debe considerar envolver esto en una función (o un método):

 def recursive_get(d, *keys): return reduce(lambda c, k: c.get(k, {}), keys, d) 

La solución de @ ThomasOrozco es correcta, pero recurre a una función lambda , que solo es necesaria para evitar TypeError si no existe una clave intermedia. Si esto no es una preocupación, puede usar dict.get directamente:

 from functools import reduce def get_from_dict(dataDict, mapList): """Iterate nested dictionary""" return reduce(dict.get, mapList, dataDict) 

Aquí hay una demostración:

 a = {'Alice': {'Car': {'Color': 'Blue'}}} path = ['Alice', 'Car', 'Color'] get_from_dict(a, path) # 'Blue' 

Si desea ser más explícito que usar lambda mientras sigue evitando TypeError , puede incluir una cláusula 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 

Finalmente, si desea boost KeyError cuando una clave no existe en ningún nivel, utilice operator.getitem o 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) 

Tenga en cuenta que [] es azúcar sintáctica para el método __getitem__ . Por lo tanto, esto se relaciona precisamente con la forma en que normalmente accedería a un valor de diccionario. El módulo del operator solo proporciona un medio más legible para acceder a este método.

No hay ninguno que yo sepa. Sin embargo, no es necesario crear una subclase de dictado, solo puede escribir una función que tome un diccionario, args y kwargs y haga lo mismo:

  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 

utilízalo así

 recursive_get(d, 'foo', 'bar') 

Realmente puedes lograr esto de manera clara en Python 3, dado su manejo de los argumentos de palabras clave predeterminadas y la descomposición de la tupla:

 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) ...: 

Un código similar también funcionará en Python 2, pero necesitarías volver a usar **kwargs , como hiciste en tu ejemplo. También necesitarías usar la indexación para descomponer *args .

En cualquier caso, no hay necesidad de un bucle si vas a hacer que la función sea recursiva de todos modos.

Puede ver que el código anterior muestra la misma funcionalidad que su método existente:

 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' 

Puede usar un código predeterminado para darle un dictado vacío sobre las claves que faltan:

 from collections import defaultdict mydict = defaultdict(dict) 

Esto solo tiene un nivel de profundidad: mydict[missingkey] es un dict vacío, mydict[missingkey][missing key] es un KeyError. Puede agregar tantos niveles como sea necesario envolviéndolo en más defaultdict , p. Ej. defaultdict(defaultdict(dict)) . También puede tener el más interno como otro valor predeterminado con una función de fábrica adecuada para su caso de uso, por ejemplo,

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

Si lo necesitas para llegar a una profundidad arbitraria, puedes hacerlo así:

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

collections.default_dict se encargará de proporcionar valores predeterminados para las claves no existentes al menos.