¿Cómo inicializo un diccionario de listas vacías en Python?
Mi intento de crear mediante programación un diccionario de listas no me permite abordar individualmente las claves del diccionario. Cada vez que creo el diccionario de listas e intento agregarlo a una clave, todas se actualizan. Aquí hay un caso de prueba muy simple:
data = {}
data = data.fromkeys(range(2),[])
data[1].append('hello')
print data
Resultado actual:{0: ['hello'], 1: ['hello']}
Resultado Esperado:{0: [], 1: ['hello']}
Esto es lo que funciona
data = {0:[],1:[]}
data[1].append('hello')
print data
Resultado real y esperado:{0: [], 1: ['hello']}
¿Por qué el fromkeys
método no funciona como se esperaba?
Cuando []
se pasa como segundo argumento a dict.fromkeys()
, todos los valores del resultado dict
serán el mismo list
objeto.
En Python 2.7 o superior, utilice un diccionario de comprensión en su lugar:
data = {k: [] for k in range(2)}
En versiones anteriores de Python, no hay comprensión de dict, pero dict
en su lugar se puede pasar una comprensión de lista al constructor:
data = dict([(k, []) for k in range(2)])
En 2.4-2.6, también es posible pasar una expresión generadora dict
y se pueden eliminar los paréntesis circundantes :
data = dict((k, []) for k in range(2))
Intente usar un defaultdict en su lugar:
from collections import defaultdict
data = defaultdict(list)
data[1].append('hello')
De esta manera, no es necesario inicializar las claves con listas vacías de antemano. En cambio, el defaultdict()
objeto llama a la función de fábrica que se le asigna cada vez que se accede a una clave que aún no existe. Entonces, en este ejemplo, intentar acceder data[1]
a los activadores data[1] = list()
internamente, dándole a esa clave una nueva lista vacía como valor.
El código original .fromkeys
comparte una lista (mutable). Similarmente,
alist = [1]
data = dict.fromkeys(range(2), alist)
alist.append(2)
print(data)
daría salida {0: [1, 2], 1: [1, 2]}
. Esto se indica en la dict.fromkeys()
documentación :
Todos los valores se refieren a una sola instancia, por lo que generalmente no tiene sentido que el valor sea un objeto mutable, como una lista vacía.
Otra opción es usar el dict.setdefault()
método , que recupera el valor de una clave después de verificar primero que existe y establecer un valor predeterminado si no es así. .append
Luego se puede invocar según el resultado:
data = {}
data.setdefault(1, []).append('hello')
Finalmente, para crear un diccionario a partir de una lista de claves conocidas y una lista de "plantilla" determinada (donde cada valor debe comenzar con los mismos elementos, pero ser una lista distinta), use un diccionario por comprensión y copie la lista inicial:
alist = [1]
data = {key: alist[:] for key in range(2)}
Aquí, alist[:]
se crea una copia superficial de alist
y esto se hace por separado para cada valor. Consulte ¿Cómo clono una lista para que no cambie inesperadamente después de la asignación? para obtener más técnicas para copiar la lista.
Podrías usar un dict de comprensión:
>>> keys = ['a','b','c']
>>> value = [0, 0]
>>> {key: list(value) for key in keys}
{'a': [0, 0], 'b': [0, 0], 'c': [0, 0]}
Esta respuesta está aquí para explicar este comportamiento a cualquiera que esté desconcertado por los resultados que obtiene al intentar crear una instancia de dict
with fromkeys()
con un valor predeterminado mutable en that dict
.
Considerar:
#Python 3.4.3 (default, Nov 17 2016, 01:08:31)
# start by validating that different variables pointing to an
# empty mutable are indeed different references.
>>> l1 = []
>>> l2 = []
>>> id(l1)
140150323815176
>>> id(l2)
140150324024968
por lo que cualquier cambio l1
no afectará l2
y viceversa. Esto sería cierto para cualquier mutable hasta ahora, incluido un dict
.
# create a new dict from an iterable of keys
>>> dict1 = dict.fromkeys(['a', 'b', 'c'], [])
>>> dict1
{'c': [], 'b': [], 'a': []}
esta puede ser una función útil. aquí asignamos a cada clave un valor predeterminado que también resulta ser una lista vacía.
# the dict has its own id.
>>> id(dict1)
140150327601160
# but look at the ids of the values.
>>> id(dict1['a'])
140150323816328
>>> id(dict1['b'])
140150323816328
>>> id(dict1['c'])
140150323816328
De hecho, ¡todos están usando la misma referencia! ¡Un cambio en uno es un cambio en todos, ya que en realidad son el mismo objeto!
>>> dict1['a'].append('apples')
>>> dict1
{'c': ['apples'], 'b': ['apples'], 'a': ['apples']}
>>> id(dict1['a'])
>>> 140150323816328
>>> id(dict1['b'])
140150323816328
>>> id(dict1['c'])
140150323816328
¡Para muchos, esto no era lo que se pretendía!
Ahora intentémoslo haciendo una copia explícita de la lista que se utiliza como valor predeterminado.
>>> empty_list = []
>>> id(empty_list)
140150324169864
y ahora crea un dictado con una copia de empty_list
.
>>> dict2 = dict.fromkeys(['a', 'b', 'c'], empty_list[:])
>>> id(dict2)
140150323831432
>>> id(dict2['a'])
140150327184328
>>> id(dict2['b'])
140150327184328
>>> id(dict2['c'])
140150327184328
>>> dict2['a'].append('apples')
>>> dict2
{'c': ['apples'], 'b': ['apples'], 'a': ['apples']}
¡Todavía no hay alegría! Escucho a alguien gritar, ¡es porque usé una lista vacía!
>>> not_empty_list = [0]
>>> dict3 = dict.fromkeys(['a', 'b', 'c'], not_empty_list[:])
>>> dict3
{'c': [0], 'b': [0], 'a': [0]}
>>> dict3['a'].append('apples')
>>> dict3
{'c': [0, 'apples'], 'b': [0, 'apples'], 'a': [0, 'apples']}
El comportamiento predeterminado de fromkeys()
es asignar None
el valor.
>>> dict4 = dict.fromkeys(['a', 'b', 'c'])
>>> dict4
{'c': None, 'b': None, 'a': None}
>>> id(dict4['a'])
9901984
>>> id(dict4['b'])
9901984
>>> id(dict4['c'])
9901984
De hecho, todos los valores son iguales (¡y los únicos!) None
. Ahora, repitamos, de una infinidad de maneras, a través de dict
y cambiemos el valor.
>>> for k, _ in dict4.items():
... dict4[k] = []
>>> dict4
{'c': [], 'b': [], 'a': []}
Mmm. ¡Se ve igual que antes!
>>> id(dict4['a'])
140150318876488
>>> id(dict4['b'])
140150324122824
>>> id(dict4['c'])
140150294277576
>>> dict4['a'].append('apples')
>>> dict4
>>> {'c': [], 'b': [], 'a': ['apples']}
Pero en realidad son diferentes []
, que en este caso era el resultado deseado.
Puedes usar esto:
l = ['a', 'b', 'c']
d = dict((k, [0, 0]) for k in l)