¿Cómo funciona collections.defaultdict?

532

He leído los ejemplos en documentos de Python, pero aún no puedo entender qué significa este método. Alguien puede ayudar? Aquí hay dos ejemplos de los documentos de Python

>>> from collections import defaultdict

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> for k in s:
...     d[k] += 1
...
>>> d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]

y

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> for k, v in s:
...     d[k].append(v)
...
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]

los parámetros inty listson para que?

Lanston
fuente
15
Por cierto, dependiendo de su caso de uso, no olvide congelar el defaultdict para el uso de solo lectura estableciendo su default_factory = Nonedespués de que haya terminado de llenar el defaultdict. Ver esta pregunta .
Acumenus

Respuestas:

598

Por lo general, un diccionario de Python arroja un KeyErrorsi intenta obtener un elemento con una clave que no está actualmente en el diccionario. Por el defaultdictcontrario, simplemente creará cualquier elemento al que intente acceder (siempre que, por supuesto, todavía no exista). Para crear dicho elemento "predeterminado", llama al objeto de función que pasa al constructor (más precisamente, es un objeto arbitrario "invocable", que incluye objetos de función y tipo). Para el primer ejemplo, los elementos predeterminados se crean utilizando int(), lo que devolverá el objeto entero 0. Para el segundo ejemplo, los elementos predeterminados se crean utilizando list(), lo que devuelve un nuevo objeto de lista vacío.

Sven Marnach
fuente
44
¿Es funcionalmente diferente a usar d.get (key, default_val)?
Ambareesh
29
@Ambareesh d.get(key, default)nunca modificará su diccionario, solo devolverá el predeterminado y dejará el diccionario sin cambios. defaultdict, por otro lado, insertará una clave en el diccionario si aún no está allí. Esta es una gran diferencia; Vea los ejemplos en la pregunta para entender por qué.
Sven Marnach
¿Cómo sabemos cuál es el valor predeterminado para cada tipo? 0 para int () y [] para list () son intuitivos, pero también puede haber tipos más complejos o autodefinidos.
Sean
1
@Sean defaultdictllama al constructor que pase. Si pasa un tipo T, los valores se construirán usando T(). No todos los tipos se pueden construir sin pasar ningún parámetro. Si desea construir dicho tipo, necesita una función de envoltura, o algo así functools.partial(T, arg1, arg2).
Sven Marnach
224

defaultdictsignifica que si no se encuentra una clave en el diccionario, en lugar de KeyErrorser arrojada, se crea una nueva entrada. El tipo de esta nueva entrada viene dado por el argumento de defaultdict.

Por ejemplo:

somedict = {}
print(somedict[3]) # KeyError

someddict = defaultdict(int)
print(someddict[3]) # print int(), thus 0
orlp
fuente
10
"El tipo de este nuevo par viene dado por el argumento de defaultdict". Tenga en cuenta que el argumento puede ser cualquier objeto invocable, no solo funciones de tipo. Por ejemplo, si foo era una función que devolvía "bar", se podría usar como argumento para dictar por defecto y si se accedía a una clave no presente, su valor se establecería en "bar".
lf215
13
O si solo desea devolver "bar": somedict = defaultdict (lambda: "bar")
Michael Scott Cuthbert
La cuarta línea devolvió 0el entero, si lo fue someddict = defaultdict(list), regresa [ ]. ¿Es 0 el entero predeterminado? O [] la lista predeterminada?
Gathide
Ninguno. 0es inmutable: en CPython todos los valores de -5a 256son caché singletons, pero este es un comportamiento específico de la implementación; en ambos casos, una nueva instancia se "crea" cada vez con int()o list(). De esa manera, d[k].append(v)puede funcionar sin llenar el diccionario con referencias a la misma lista, lo que haría defaultdictcasi inútil. Si este fuera el comportamiento, defaultdicttomaría un valor, no una lambda, como parámetro. (¡Perdón por la terrible explicación!)
wizzwizz4
93

defaultdict

"El diccionario estándar incluye el método setdefault () para recuperar un valor y establecer un valor predeterminado si el valor no existe. Por el contrario, defaultdictpermite que la persona que llama especifique el valor predeterminado (valor que se devolverá) por adelantado cuando se inicializa el contenedor".

como lo definió Doug Hellmann en The Python Standard Library con Example

Cómo usar defaultdict

Importar defaultdict

>>> from collections import defaultdict

Inicializar defaultdict

Inicialízalo pasando

invocable como su primer argumento (obligatorio)

>>> d_int = defaultdict(int)
>>> d_list = defaultdict(list)
>>> def foo():
...     return 'default value'
... 
>>> d_foo = defaultdict(foo)
>>> d_int
defaultdict(<type 'int'>, {})
>>> d_list
defaultdict(<type 'list'>, {})
>>> d_foo
defaultdict(<function foo at 0x7f34a0a69578>, {})

** kwargs como su segundo argumento (opcional)

>>> d_int = defaultdict(int, a=10, b=12, c=13)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

o

>>> kwargs = {'a':10,'b':12,'c':13}
>>> d_int = defaultdict(int, **kwargs)
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12})

Cómo funciona

Como es una clase secundaria de diccionario estándar, puede realizar las mismas funciones.

Pero en caso de pasar una clave desconocida, devuelve el valor predeterminado en lugar de error. Por ej .:

>>> d_int['a']
10
>>> d_int['d']
0
>>> d_int
defaultdict(<type 'int'>, {'a': 10, 'c': 13, 'b': 12, 'd': 0})

En caso de que desee cambiar el valor predeterminado, sobrescriba default_factory:

>>> d_int.default_factory = lambda: 1
>>> d_int['e']
1
>>> d_int
defaultdict(<function <lambda> at 0x7f34a0a91578>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0})

o

>>> def foo():
...     return 2
>>> d_int.default_factory = foo
>>> d_int['f']
2
>>> d_int
defaultdict(<function foo at 0x7f34a0a0a140>, {'a': 10, 'c': 13, 'b': 12, 'e': 1, 'd': 0, 'f': 2})

Ejemplos en la pregunta

Ejemplo 1

Como int se ha pasado como default_factory, cualquier clave desconocida devolverá 0 por defecto.

Ahora, a medida que se pasa la cadena en el bucle, aumentará el recuento de esos alfabetos en d.

>>> s = 'mississippi'
>>> d = defaultdict(int)
>>> d.default_factory
<type 'int'>
>>> for k in s:
...     d[k] += 1
>>> d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]
>>> d
defaultdict(<type 'int'>, {'i': 4, 'p': 2, 's': 4, 'm': 1})

Ejemplo 2

Como se ha pasado una lista como default_factory, cualquier clave desconocida (inexistente) devolverá [] (es decir, lista) por defecto.

Ahora, a medida que se pasa la lista de tuplas en el bucle, se agregará el valor en d [color]

>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
>>> d = defaultdict(list)
>>> d.default_factory
<type 'list'>
>>> for k, v in s:
...     d[k].append(v)
>>> d.items()
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
>>> d
defaultdict(<type 'list'>, {'blue': [2, 4], 'red': [1], 'yellow': [1, 3]})
Somendra Joshi
fuente
20

Los diccionarios son una forma conveniente de almacenar datos para su posterior recuperación por nombre (clave). Las claves deben ser objetos únicos e inmutables y, por lo general, son cadenas. Los valores en un diccionario pueden ser cualquier cosa. Para muchas aplicaciones, los valores son tipos simples, como enteros y cadenas.

Se vuelve más interesante cuando los valores en un diccionario son colecciones (listas, dictos, etc.) En este caso, el valor (una lista vacía o dict) debe inicializarse la primera vez que se usa una clave determinada. Si bien esto es relativamente fácil de hacer manualmente, el tipo defaultdict automatiza y simplifica este tipo de operaciones. Un defaultdict funciona exactamente como un dict normal, pero se inicializa con una función ("fábrica predeterminada") que no toma argumentos y proporciona el valor predeterminado para una clave inexistente.

Un fallo predeterminado nunca generará un KeyError. Cualquier clave que no exista obtiene el valor devuelto por la fábrica predeterminada.

from collections import defaultdict
ice_cream = defaultdict(lambda: 'Vanilla')

ice_cream['Sarah'] = 'Chunky Monkey'
ice_cream['Abdul'] = 'Butter Pecan'

print(ice_cream['Sarah'])
>>>Chunky Monkey

print(ice_cream['Joe'])
>>>Vanilla

Aquí hay otro ejemplo sobre cómo usar defaultdict, podemos reducir la complejidad

from collections import defaultdict
# Time complexity O(n^2)
def delete_nth_naive(array, n):
    ans = []
    for num in array:
        if ans.count(num) < n:
            ans.append(num)
    return ans

# Time Complexity O(n), using hash tables.
def delete_nth(array,n):
    result = []
    counts = defaultdict(int)

    for i in array:
        if counts[i] < n:
            result.append(i)
            counts[i] += 1
    return result


x = [1,2,3,1,2,1,2,3]
print(delete_nth(x, n=2))
print(delete_nth_naive(x, n=2))

En conclusión, siempre que necesite un diccionario, y el valor de cada elemento debe comenzar con un valor predeterminado, use un valor predeterminado.

dimensión
fuente
18

Aquí hay una gran explicación de los defaultdicts: http://ludovf.net/blog/python-collections-defaultdict/

Básicamente, los parámetros int y list son funciones que pasa. Recuerde que Python acepta nombres de funciones como argumentos. int devuelve 0 por defecto y list devuelve una lista vacía cuando se llama entre paréntesis.

En los diccionarios normales, si en su ejemplo intento llamar d[a], obtendré un error (KeyError), ya que solo existen las teclas m, s, i y p y la tecla a no se ha inicializado. Pero en un caso por defecto, toma el nombre de una función como argumento, cuando intenta usar una clave que no se ha inicializado, simplemente llama a la función que ingresó y asigna su valor de retorno como el valor de la nueva clave.

varagrawal
fuente
7

Dado que la pregunta es sobre "cómo funciona", algunos lectores pueden querer ver más detalles. Específicamente, el método en cuestión es el __missing__(key)método. Ver: https://docs.python.org/2/library/collections.html#defaultdict-objects .

Más concretamente, esta respuesta muestra cómo utilizarla de __missing__(key)manera práctica: https://stackoverflow.com/a/17956989/1593924

Para aclarar qué significa 'invocable', aquí hay una sesión interactiva (desde 2.7.6, pero también debería funcionar en v3):

>>> x = int
>>> x
<type 'int'>
>>> y = int(5)
>>> y
5
>>> z = x(5)
>>> z
5

>>> from collections import defaultdict
>>> dd = defaultdict(int)
>>> dd
defaultdict(<type 'int'>, {})
>>> dd = defaultdict(x)
>>> dd
defaultdict(<type 'int'>, {})
>>> dd['a']
0
>>> dd
defaultdict(<type 'int'>, {'a': 0})

Ese fue el uso más típico de defaultdict (excepto el uso sin sentido de la variable x). Puede hacer lo mismo con 0 como valor predeterminado explícito, pero no con un valor simple:

>>> dd2 = defaultdict(0)

Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    dd2 = defaultdict(0)
TypeError: first argument must be callable

En cambio, lo siguiente funciona porque pasa una función simple (crea sobre la marcha una función sin nombre que no toma argumentos y siempre devuelve 0):

>>> dd2 = defaultdict(lambda: 0)
>>> dd2
defaultdict(<function <lambda> at 0x02C4C130>, {})
>>> dd2['a']
0
>>> dd2
defaultdict(<function <lambda> at 0x02C4C130>, {'a': 0})
>>> 

Y con un valor predeterminado diferente:

>>> dd3 = defaultdict(lambda: 1)
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {})
>>> dd3['a']
1
>>> dd3
defaultdict(<function <lambda> at 0x02C4C170>, {'a': 1})
>>> 
Jon Coombs
fuente
7

My own 2 ¢: también puedes subclase defaultdict:

class MyDict(defaultdict):
    def __missing__(self, key):
        value = [None, None]
        self[key] = value
        return value

Esto podría ser útil para casos muy complejos.

Edward Falk
fuente
4

El comportamiento de defaultdictse puede imitar fácilmente en dict.setdefaultlugar de d[key]en cada llamada.

En otras palabras, el código:

from collections import defaultdict

d = defaultdict(list)

print(d['key'])                        # empty list []
d['key'].append(1)                     # adding constant 1 to the list
print(d['key'])                        # list containing the constant [1]

es equivalente a:

d = dict()

print(d.setdefault('key', list()))     # empty list []
d.setdefault('key', list()).append(1)  # adding constant 1 to the list
print(d.setdefault('key', list()))     # list containing the constant [1]

La única diferencia es que, usando defaultdict, el constructor de la lista se llama solo una vez, y usando dict.setdefaultel constructor de la lista se llama con más frecuencia (pero el código puede reescribirse para evitar esto, si es realmente necesario).

Algunos pueden argumentar que hay una consideración de rendimiento, pero este tema es un campo minado. Esta publicación muestra que no hay una gran ganancia de rendimiento al usar defaultdict, por ejemplo.

En mi opinión, defaultdict es una colección que agrega más confusión que beneficios al código. Inútil para mí, pero otros pueden pensar diferente.

Diego Queiroz
fuente
3

La herramienta defaultdict es un contenedor en la clase de colecciones de Python. Es similar al contenedor habitual del diccionario (dict), pero tiene una diferencia: el tipo de datos de los campos de valor se especifica en la inicialización.

Por ejemplo:

from collections import defaultdict

d = defaultdict(list)

d['python'].append("awesome")

d['something-else'].append("not relevant")

d['python'].append("language")

for i in d.items():

    print i

Esto imprime:

('python', ['awesome', 'language'])
('something-else', ['not relevant'])
saarthak johari
fuente
"El tipo de datos de los campos de valor se especifica en la inicialización": esto no es correcto. Se proporciona una función de fábrica de elementos. Aquí listestá la función para llamar para completar un valor faltante, no el tipo de los objetos para crear. Por ejemplo, para tener un valor predeterminado de 1, usaría el lambda:1que obviamente no es un tipo.
asac
2

Creo que es mejor usarlo en lugar de una declaración de cambio de caso. Imagínese si tenemos una declaración de cambio de caso de la siguiente manera:

option = 1

switch(option) {
    case 1: print '1st option'
    case 2: print '2nd option'
    case 3: print '3rd option'
    default: return 'No such option'
}

No hay switchdeclaraciones de casos disponibles en python. Podemos lograr lo mismo usando defaultdict.

from collections import defaultdict

def default_value(): return "Default Value"
dd = defaultdict(default_value)

dd[1] = '1st option'
dd[2] = '2nd option'
dd[3] = '3rd option'

print(dd[4])    
print(dd[5])    
print(dd[3])

Imprime:

Default Value
Default Value
3rd option

En el fragmento anterior ddno tiene las teclas 4 o 5 y, por lo tanto, imprime un valor predeterminado que hemos configurado en una función auxiliar. Esto es bastante mejor que un diccionario sin formato donde KeyErrorse arroja a si la clave no está presente. A partir de esto, es evidente que es defaultdictmás como una declaración de caso de cambio donde podemos evitar if-elif-elif-elsebloques complicados .

Un buen ejemplo más que me impresionó mucho de este sitio es:

>>> from collections import defaultdict
>>> food_list = 'spam spam spam spam spam spam eggs spam'.split()
>>> food_count = defaultdict(int) # default value of int is 0
>>> for food in food_list:
...     food_count[food] += 1 # increment element's value by 1
...
defaultdict(<type 'int'>, {'eggs': 1, 'spam': 7})
>>>

Si intentamos acceder a cualquier elemento que no sea eggsy spamobtendremos un recuento de 0.

Swadhikar C
fuente
2

Sin defaultdict, probablemente puede asignar nuevos valores a claves invisibles, pero no puede modificarlo. Por ejemplo:

import collections
d = collections.defaultdict(int)
for i in range(10):
  d[i] += i
print(d)
# Output: defaultdict(<class 'int'>, {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9})

import collections
d = {}
for i in range(10):
  d[i] += i
print(d)
# Output: Traceback (most recent call last): File "python", line 4, in <module> KeyError: 0
Ming Liu
fuente
2

Bueno, defaultdict también puede generar keyerror en el siguiente caso:

    from collections import defaultdict
    d = defaultdict()
    print(d[3]) #raises keyerror

Recuerde siempre dar argumento al defaultdict como defaultdict (int).

Shweta Sharma
fuente
0

El diccionario estándar incluye el método setdefault () para recuperar un valor y establecer un valor predeterminado si el valor no existe. Por el contrario, defaultdict permite a la persona que llama especificar el valor predeterminado por adelantado cuando se inicializa el contenedor.

import collections

def default_factory():
    return 'default value'

d = collections.defaultdict(default_factory, foo='bar')
print 'd:', d
print 'foo =>', d['foo']
print 'bar =>', d['bar']

Esto funciona bien siempre que sea apropiado que todas las claves tengan el mismo valor predeterminado. Puede ser especialmente útil si el valor predeterminado es un tipo utilizado para agregar o acumular valores, como una lista, un conjunto o incluso int. La documentación de la biblioteca estándar incluye varios ejemplos de uso de defaultdict de esta manera.

$ python collections_defaultdict.py

d: defaultdict(<function default_factory at 0x100468c80>, {'foo': 'bar'})
foo => bar
bar => default value

fuente
0

En breve:

defaultdict(int) - el argumento int indica que los valores serán de tipo int.

defaultdict(list) - la lista de argumentos indica que los valores serán de tipo lista.

Shravan kp
fuente