2010-07-29 20 views
9

Me pregunto cómo crear un diccionario indulgente (uno que devuelve un valor predeterminado si se produce un error clave).Un diccionario indulgente

En el siguiente ejemplo de código obtendría un KeyError; por ejemplo

a = {'one':1,'two':2} 
print a['three'] 

Con el fin de no conseguir uno que se lo 1. Tienen que capturar la excepción o utilizar conseguir.

me gustaría no tener que hacer eso con mi diccionario ...

+2

'collections.defaultdict' es su solución con pilas incluidas. –

+1

+1 para el título de pregunta –

Respuesta

22
import collections 
a = collections.defaultdict(lambda: 3) 
a.update({'one':1,'two':2}) 
print a['three'] 

emite 3 según sea necesario. También podría subclase dict mismo y anular __missing__, pero eso no tiene mucho sentido cuando el comportamiento defaultdict (haciendo caso omiso de la llave perdida exacta que está siendo alzó la vista) que se adapte tan bien ...

Editar ... , a menos que, es decir, está preocupado por a creciendo cada vez que busca una clave faltante (que es parte de la semántica de defaultdict) y prefiere obtener un comportamiento más lento pero ahorrar algo de memoria. Por ejemplo, en términos de memoria ...:

>>> import sys 
>>> a = collections.defaultdict(lambda: 'blah') 
>>> print len(a), sys.getsizeof(a) 
0 140 
>>> for i in xrange(99): _ = a[i] 
... 
>>> print len(a), sys.getsizeof(a) 
99 6284 

... la defaultdict, inicialmente vacío, ahora tiene el 99 llaves previamente faltantes que miramos hacia arriba, y tiene 6284 bytes (frente a los 140 bytes tomó cuando estaba vacío).

El enfoque alternativo ...:

>>> class mydict(dict): 
... def __missing__(self, key): return 3 
... 
>>> a = mydict() 
>>> print len(a), sys.getsizeof(a) 
0 140 
>>> for i in xrange(99): _ = a[i] 
... 
>>> print len(a), sys.getsizeof(a) 
0 140 

... ahorra completo esta sobrecarga de la memoria, como ves. Por supuesto, el rendimiento es otra cuestión:

$ python -mtimeit -s'import collections; a=collections.defaultdict(int); r=xrange(99)' 'for i in r: _=a[i]' 
100000 loops, best of 3: 14.9 usec per loop 

$ python -mtimeit -s'class mydict(dict): 
> def __missing__(self, key): return 0 
> ' -s'a=mydict(); r=xrange(99)' 'for i in r: _=a[i]' 
10000 loops, best of 3: 92.9 usec per loop 

Desde defaultdict añade la tecla (previamente perdidos) en las operaciones de búsqueda, se hace mucho más rápido cuando dicha clave está próxima la mirada, mientras mydict (que anula __missing__ para evitar que Además) paga la "sobrecarga de búsqueda de clave faltante" cada vez.

Si usted se preocupa por cualquier problema (rendimiento vs huella de memoria) depende completamente de su caso de uso específico, por supuesto. Se es en cualquier caso una buena idea estar al tanto de la desventaja -)

+3

Advertencia: defaultdict inserta un nuevo elemento en sí mismo cada vez que devuelve el valor predeterminado para una clave determinada. Eso convierte las operaciones de lectura en posibles operaciones de escritura, y significa que buscar muchas teclas faltantes hará que crezca rápidamente. http://docs.python.org/library/collections.html#collections.defaultdict.__missing__ –

+0

@Forest, buen punto! Déjame editar en consecuencia. –

+0

¡Excelente publicación! Su penúltimo párrafo no parece estar relacionado con su ejemplo, ya que nunca usa la misma clave dos veces. Por lo tanto, parece que el incumplimiento es más rápido incluso si nunca repites una tecla y aún más rápido si lo haces. ¿Está bien? –

7

Nuevo en la versión 2.5: Si una subclase de dict define un __ método __missing(), si la clave clave no es presente, la operación d [clave] llama a ese método con la tecla clave como argumento. La operación d [tecla] d [devuelve] o aumenta lo que se devuelve o aumenta con __missing __ (tecla) si la clave no está presente. Ninguna otra operación o métodos invocan __missing __(). Si __missing __() no está definido, KeyError se genera. __missing __() debe ser un método; no puede ser una variable de instancia. Para un ejemplo de , vea collections.defaultdict.

http://docs.python.org/library/stdtypes.html

3

Es probable que desee utilizar un defaultdict (Requiere al menos python2.5 creo)

from collections import defaultdict 
def default(): return 'Default Value' 
d = defaultdict(default) 
print(d['?']) 

La función que se pasa al constructor le dice a la clase qué regresar como un valor predeterminado. Ver the documentation para ejemplos adicionales.

5

Aquí es cómo subclase dict según lo sugerido por NullUserException

>>> class forgiving_dict(dict): 
...  def __missing__(self, key): 
...   return 3 
... 
>>> a = forgiving_dict() 
>>> a.update({'one':1,'two':2}) 
>>> print a['three'] 
3 

Una gran diferencia entre esta respuesta y Alex es que la clave que falta es no añadido al diccionario

>>> print a 
{'two': 2, 'one': 1} 

Qué es bastante significativo si espera una gran cantidad de fallas

0

A veces lo que eres ally want es .setdefault() que no es muy intuitivo, pero es un método que "devuelve la clave especificada, si no existe, configure esa clave para este valor".

Aquí es un ejemplo de setdefault() siendo utilizado con buenos resultados:

collection = {} 
for elem in mylist: 
    key = key_from_elem(elem) 
    collection.setdefault(key, []).append(elem) 

Esto nos permitirá crear un diccionario como: {'key1':[elem1, elem3], 'key2':[elem3]} sin necesidad de tener un control feo para ver si hay una clave ya existe y creando una lista para eso.