2010-06-08 13 views
17

vorrei inizializzare un dizionario di insiemi (in Python 2.6) utilizzando dict.fromkeys, ma la struttura risultante comporta in modo strano. Più in particolare:comportamento indesiderato da 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])} 

Io, ovviamente, non voglio aggiungere 3 a tutti i set, solo per il set che corrisponde a x[5]. Naturalmente, posso evitare il problema inizializzazione x senza fromkeys, ma mi piacerebbe capire che cosa mi manca qui.

+4

Sono tutti lo stesso set. Insiemi, elenchi, dizionari e qualsiasi altro oggetto sono tipi di riferimento e quando li si assegna a un'altra variabile, viene copiato solo il riferimento, non l'oggetto reale. 'Fromkeys' deve utilizzare l'assegnazione per associare l'insieme a ciascun tasto, ma come puoi vedere, questo non copia il set. Non sono sicuro di come aggirare questo, oltre a creare il dizionario in un modo diverso. –

risposta

14

Il secondo argomento dict.fromkeys è solo un valore. È stato creato un dizionario con lo stesso impostato come valore per ogni chiave. Presumibilmente capisci come funziona:

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

stai vedendo lo stesso comportamento lì; nel tuo caso, x[0], x[1], x[2] (ecc) sono tutti i diversi modi per accedere esattamente lo stesso set oggetto.

Questo è un po 'più facile da vedere con gli oggetti la cui rappresentazione di stringa include il loro indirizzo di memoria, dove si può vedere che sono identici:

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

La ragione per il suo lavoro in questo modo è che set([]) crea un oggetto (un oggetto impostato). Fromkeys utilizza quindi quell'oggetto specifico per creare tutte le sue voci di dizionario. Considerare:

>>> 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 

Tutti i set sono uguali!

+1

Dovresti davvero confrontare le identità: 'x [0] è x [1]'. –

3

A causa della this dal dictobject.c:

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

Il value è il suo "set ([])", viene valutato solo una volta quindi il loro numero di oggetto di riferimento risultato viene incrementato e aggiunto al dizionario, non lo valuta ogni volta che aggiunge al dett.

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

Non c'è bisogno di 'deepcopy'. 'x [n] = set()' crea un nuovo set per ogni valore. –

13

Si può fare questo con un generatore di espressione:

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

In Python 3, è possibile utilizzare una comprensione dizionario:

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

In entrambi i casi, l'espressione set() viene valutata per ogni elemento, invece di essere valutato una volta e copiato su ciascun elemento.

+0

OK, grazie! –

+1

+1 per fornire la soluzione anche dopo che la risposta accettata lo ha spiegato bene. – Randy

+0

Se invece di set voglio inizializzare gli elenchi, x = {i: [] per i in range (10)} causa SyntaxError mentre il dict ((i, []) per i in range (10)) non lo fa. – Eduardo

Problemi correlati