Collections.defaultdict diferencia con dict normal

12 minutos de lectura

Avatar de usuario de Lanston
Lanston

He leído los ejemplos en documentos de python, pero todavía 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()
dict_items([('m', 1), ('i', 4), ('s', 4), ('p', 2)])

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 parametros int y list son para que?

  • Por cierto, dependiendo de su caso de uso, no lo olvide para congelar el dictamen predeterminado para uso de solo lectura configurando su default_factory = None después de que haya terminado de completar el dictado predeterminado. Ver esta pregunta.

    – Asclepio

    30 de octubre de 2016 a las 5:54


  • Ver también: stackoverflow.com/questions/17215400/…

    – drftymac

    10/10/2017 a las 21:05

Avatar de usuario de Sven Marnach
Sven Marnach

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

  • ¿Es funcionalmente diferente que usar d.get(key, default_val) ?

    – Ambareesh

    1 de mayo de 2019 a las 1:31

  • @Ambareesh d.get(key, default) nunca modificará su diccionario, simplemente devolverá el valor predeterminado y dejará el diccionario sin cambios. defaultdictpor otro lado, será 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

    2 mayo 2019 a las 19:10

  • ¿Cómo sabemos cuál es el valor predeterminado para cada tipo? 0 para int() y [] for list() son intuitivos, pero también puede haber tipos más complejos o autodefinidos.

    – sean

    11 de marzo de 2020 a las 10:40


  • @Sean defaultdict llama a cualquier constructor que le pases. Si le pasas un tipo Tlos valores se construirán usando T(). No todos los tipos se pueden construir sin pasar ningún parámetro. Si desea construir un tipo de este tipo, necesita una función contenedora, o algo así functools.partial(T, arg1, arg2).

    – Sven Marnach

    11 de marzo de 2020 a las 10:58

  • O aún más fácilmente: una lambda. defaultdict(lambda : T(arg1, arg2)).

    – Mees de Vries

    18 de agosto de 2020 a las 11:24

avatar de usuario de orlp
orlp

defaultdict significa que si una clave no se encuentra en el diccionario, entonces en lugar de una KeyError siendo lanzado, 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

  • “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 fuera una función que devolviera “barra”, foo podría usarse como argumento para el dictado predeterminado y si se accedió a una clave no presente, su valor se establecería en “barra”.

    – lf215

    29 de julio de 2013 a las 5:56


  • O si solo desea devolver “barra”: somedict = defaultdict(lambda:”bar”)

    –Michael Scott Asato Cuthbert

    2 jun 2014 a las 21:23

  • Cuarta línea devuelta 0 el entero, si fuera someddict = defaultdict(list) vuelve [ ]. ¿Es 0 el número entero predeterminado? O [ ] la lista predeterminada?

    – Gathide

    5 de enero de 2017 a las 7:30


  • Ni. 0 es inmutable – en CPython todos los valores de -5 a 256 son singletons almacenados en caché, pero este es un comportamiento específico de la implementación; en ambos casos, se “crea” una nueva instancia 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 representaría defaultdict casi inútil Si este fuera el comportamiento, defaultdict tomaría un valor, no una lambda, como parámetro. (¡Perdón por la terrible explicación!)

    – wizzwizz4

    7 de octubre de 2017 a las 8:58

Avatar de usuario de Somendra Joshi
Somendra Joshi

predeterminadodict

“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 que la persona que llama especifique el valor predeterminado (valor que se devolverá) por adelantado cuando se inicializa el contenedor”.

Según lo definido por Doug Hellmann en La biblioteca estándar de Python por ejemplo

Cómo usar el dictado predeterminado

Importar dictamen predeterminado

>>> from collections import defaultdict

Inicializar dictado predeterminado

Inicializarlo 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 del diccionario estándar, puede realizar todas las mismas funciones.

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

>>> 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 de forma predeterminada.

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) regresará [ ](es decir, lista) por defecto.

Ahora, a medida que la lista de tuplas se pasa en el ciclo, agregará el valor en el 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]})

  • Gracias por la respuesta. ¿Sabes cómo hacer que la constante sea siempre diferente? Yo explico: defaultdict(lambda: 'string', **kwargs) no funcionará como se esperaba porque todas las claves nuevas compartirán la misma instancia de ‘cadena’. ¿Cómo puedo proporcionar una copia cada vez? Tenga en cuenta que defaultdict(lambda: copy.copy('string'), **kwargs) no funciona porque la copia se evalúa solo una vez.

    – Dr_Zaszuś

    23 de junio de 2020 a las 9:48


avatar de usuario de dimensión
dimensión

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, dictados, etc.). En este caso, el valor (una lista vacía o dictado) 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 dictado predeterminado funciona exactamente como un dictado normal, pero se inicializa con una función (“fábrica predeterminada”) que no acepta argumentos y proporciona el valor predeterminado para una clave inexistente.

Un dictado predeterminado nunca generará un KeyError. Cualquier clave que no existe 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, cada vez que necesite un diccionario, y el valor de cada elemento debe comenzar con un valor predeterminado, use un diccionario predeterminado.

avatar de usuario de varagrawal
varagraval

Hay una gran explicación de los dictados predeterminados aquí: http://ludovf.net/blog/python-collections-defaultdict/

Básicamente, los parámetros En t y lista son funciones que te pasan. Recuerda que Python acepta nombres de funciones como argumentos. En t devuelve 0 por defecto y lista devuelve una lista vacía cuando se llama entre paréntesis.

En diccionarios normales, si en tu 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 dictado predeterminado, toma un nombre de función como argumento, cuando intenta usar una clave que no se ha inicializado, simplemente llama a la función que pasó y asigna su valor de retorno como el valor de la nueva clave.

Avatar de usuario de Diego Queiroz
Diego Queiroz

el comportamiento de defaultdict se puede imitar fácilmente usando dict.setdefault en lugar 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 defaultdictel constructor de listas se llama solo una vez, y usando dict.setdefault el constructor de la lista se llama con más frecuencia (pero el código se puede reescribir 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.

Avatar de usuario de la comunidad
Comunidad

Dado que la pregunta es sobre “cómo funciona”, algunos lectores pueden querer ver más detalles. En concreto, 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 hacer uso de __missing__(key) de forma práctica: https://stackoverflow.com/a/17956989/1593924

Para aclarar qué significa ‘llamable’, aquí hay una sesión interactiva (de 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 por el uso inútil 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 acepta 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})
>>> 

¿Ha sido útil esta solución?