Comportamiento no deseado de dict.fromkeys

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:

 >>>> 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])} 

Obviamente no quiero agregar 3 a todos los conjuntos, solo al conjunto que corresponde a x[5] . Por supuesto, puedo evitar el problema al inicializar x sin fromkeys , pero me gustaría entender lo que me estoy perdiendo aquí.

El segundo argumento de dict.fromkeys es solo un valor. Ha creado un diccionario que tiene el mismo conjunto que el valor para cada clave. Es de suponer que entiendes cómo funciona esto:

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

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

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

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

Puedes hacer esto con una expresión generadora:

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

En Python 3, puedes usar un diccionario de comprensión:

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

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

Debido a esto desde el 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 “conjunto ([])”, se evalúa solo una vez, luego el recuento de referencias del objeto resultante se incrementa y se agrega al diccionario, no se evalúa cada vez que se agrega al dict.

 #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([])} 

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

 >>> 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 conjuntos son iguales!