2010-06-08 16 views
17

Me gustaría inicializar un diccionario de conjuntos (en Python 2.6) usando dict.fromkeys, pero la estructura resultante se comporta de manera extraña. Más específicamente:Comportamiento no deseado de dict.fromkeys

>>>> x = {}.fromkeys(range(10), set([])) 
>>>> x 
{0: set([]), 1: set([]), 2: set([]), 3: set([]), 4: set([]), 5: set([]), 6: set([]), 7: set([]), 8: set([]), 9: set([])} 
>>>> x[5].add(3) 
>>>> x 
{0: set([3]), 1: set([3]), 2: set([3]), 3: set([3]), 4: set([3]), 5: set([3]), 6: set([3]), 7: set([3]), 8: set([3]), 9: set([3])} 

Yo, obviamente, no quiero añadir 3 a todos los conjuntos, sólo para el conjunto que corresponde a x[5]. Por supuesto, puedo evitar el problema inicializando x sin fromkeys, pero me gustaría entender lo que me falta aquí.

+4

Son todos el mismo conjunto. Los conjuntos, listas, diccionarios y cualquier otro objeto son tipos de referencia, y cuando los asigna a otra variable, solo se copia la referencia, no el objeto real. 'fromkeys' debe usar la asignación para asociar el conjunto con cada clave, pero como puede ver, esto no copia el conjunto. No estoy seguro de cómo evitar esto, además de crear el diccionario de otra manera. –

Respuesta

14

El segundo argumento para dict.fromkeys es solo un valor. Ha creado un diccionario que tiene el mismo establecido como el valor de cada tecla. Presumiblemente usted entiende la forma en que esto funciona:

>>> a = set() 
>>> b = a 
>>> b.add(1) 
>>> b 
set([1]) 
>>> a 
set([1]) 

está viendo el mismo comportamiento allí; en su caso, x[0], x[1], x[2] (etc.) son todas formas diferentes de acceder exactamente al mismo objeto set.

Esto es un poco más fácil de ver con objetos cuya representación de cadena incluye su dirección de memoria, donde se puede ver que son idénticos:

>>> dict.fromkeys(range(2), object()) 
{0: <object object at 0x1001da080>, 
1: <object object at 0x1001da080>} 
0

La razón de su trabajo de esta manera es que set([]) crea un objeto (un objeto establecido). Fromkeys luego usa ese objeto específico para crear todas sus entradas de diccionario. Considere:

>>> x 
{0: set([]), 1: set([]), 2: set([]), 3: set([]), 4: set([]), 5: set([]), 
6: set([]), 7: set([]), 8: set([]), 9: set([])} 
>>> x[0] is x[1] 
True 

¡Todos los juegos son iguales!

+1

Realmente debería estar comparando identidades: 'x [0] es x [1]'. –

3

Debido a this de la dictobject.c:

while (_PyDict_Next(seq, &pos, &key, &oldvalue, &hash)) 
{ 
      Py_INCREF(key); 
      Py_INCREF(value); 
      if (insertdict(mp, key, hash, value)) 
       return NULL; 
} 

El value es su "SET ([])", se evalúa solamente una vez entonces su resultado del recuento referencia de objeto se incrementa y se añade al diccionario, no lo evalúa cada vez que se agrega al dict.

0

#To do what you want: 

import copy 
s = set([]) 
x = {} 
for n in range(0,5): 
    x[n] = copy.deepcopy(s) 
x[2].add(3) 
print x 

#Printing 
#{0: set([]), 1: set([]), 2: set([3]), 3: set([]), 4: set([])} 
+2

No es necesario 'deepcopy'. 'x [n] = set()' crea un nuevo conjunto para cada valor. –

13

Usted puede hacer esto con una expresión generadora:

x = dict((i,set()) for i in range(10)) 

En Python 3, se puede utilizar un diccionario por comprensión:

x = { i : set() for i in range(10) } 

En ambos casos, la expresión se evalúa set() para cada elemento, en lugar de ser evaluado una vez y copiado a cada elemento.

+0

¡Muy bien, gracias! –

+1

+1 para proporcionar la solución incluso después de que la respuesta aceptada lo haya explicado bien. – Randy

+0

Si en lugar de conjuntos quiero inicializar listas, x = {i: [] para i en el rango (10)} causa SyntaxError mientras que el dict ((i, []) para i en rango (10)) no lo hace. – Eduardo

Cuestiones relacionadas