2010-10-27 9 views
7

Ho un codice Python che genera un'eccezione KeyError. Finora non sono stato in grado di riprodurre al di fuori dell'ambiente operativo, quindi non posso pubblicare qui un caso di test ridotto.Come può "k in d" essere False, ma "k in d.keys()" essere True?

Il codice che sta sollevando l'eccezione viene iterazione attraverso un ciclo come questo:

for k in d.keys(): 
    if condition: 
     del d[k] 

La linea del[k] genera l'eccezione. Ho aggiunto una clausola try/except e sono stato in grado di determinare che k in d è False, ma k in d.keys() è True.

Le chiavi di d sono metodi associati di istanze di classi vecchio stile.

La classe implementa __cmp__ e __hash__, quindi è qui che ho concentrato la mia attenzione.

+0

Bene, se ora che cosa 'k' sta causando i problemi, perché non solo vedere se esistono in' d.keys() 'e in' d'? – SilentGhost

+1

Permettetemi di chiarire, se ignorate l'iterazione e semplicemente testate il dizionario, c'è una chiave per cui 'k in d' è vero ma' k in d.keys() 'è falso? Cioè l'iterazione è irrilevante per il problema? – katrielalex

+0

Puoi mostrare la tua funzione '__hash__'? – user470379

risposta

18

k in d.keys() metterà alla prova l'uguaglianza in modo iterativo per ogni tasto, mentre k in d utilizza __hash__, pertanto il tuo __hash__ potrebbe essere danneggiato (ovvero restituisce hash diversi per oggetti che si equivalgono).

+0

'__hash__' è buildbot.util.ComparableMixin .__ hash__ di buildbot, che esamina il confronto_attr di un'istanza per generare un valore di hash. Questi valori stavano cambiando nel tempo, quindi l'hash dell'oggetto non era stabile per tutta la sua durata. –

+0

Sì, questo è un altro modo per rompere "__hash__". Ma allora non ha nemmeno senso usare l'oggetto come chiave del dizionario. – adw

+0

Sì, non è il mio codice che sta mettendo l'oggetto come chiave del dizionario (un) per fortuna. –

4

Non eliminare gli elementi in d mentre l'iterazione su di esso, memorizzare le chiavi che si desidera eliminare in un elenco ed eliminarli in un altro ciclo:

deleted = [] 
for k in d.keys(): 
    if condition: 
     deleted.append(k) 
for k in deleted: 
    del d[k] 
+2

non sta iterando su 'd', sta iterando su' d.keys() 'lista – SilentGhost

+0

@SilentGhost se le chiavi sono pigri (che sospetto siano ma non posso confermare al momento), allora è effettivamente la stessa cosa in questo contesto. – Davy8

+0

Cosa intendi per pigro carico? è una lista. – SilentGhost

-1

Quello che stai facendo genererebbe un'eccezione di modifica simultanea in Java. d.keys() crea un elenco delle chiavi così come esistono quando viene chiamato, ma l'elenco è ora statico: le modifiche a d non modificheranno una versione archiviata di d.keys(). Quindi, quando si itera su d.keys() ma si eliminano elementi, si finisce con la possibilità di modificare una chiave che non c'è più.

È possibile utilizzare d.pop(k, None), che restituirà il valore mappato a k o None, se k non è presente. Ciò evita il problema KeyError.

MODIFICA: per maggiore chiarezza, per prevenire più downmod fantasma (nessun problema con feedback negativo, basta renderlo costruttivo e lasciare un commento in modo che possiamo avere una discussione potenzialmente informativo - Sono qui per imparare e aiutare):

È vero che in questa particolare condizione non si deve incasinare. Lo stavo solo considerando come un potenziale problema, perché se sta usando lo stesso tipo di schema di codifica in un'altra parte del programma in cui non è così attento/fortunato a come sta trattando la struttura dati, tali problemi potrebbero sorgere . Non sta nemmeno usando un dizionario, ma piuttosto una classe che implementa determinati metodi in modo da poterlo trattare in modo simile.

+4

No, non è vero - se si usa sempre 'del d [k]' per 'k' in' d.keys() ', non si eliminerà mai una chiave due volte, poiché i tasti sono univoci. – katrielalex

+1

Supponendo che tutto sia come sopra, le chiavi sono univoche in un dizionario, e cancella le chiavi una sola volta per ogni iterazione in modo da non eliminare le chiavi che è già stato cancellato. – user470379

+0

Beh sì, è vero che in questa particolare condizione non dovrebbe essere incasinato. Lo stavo facendo notare come un potenziale problema. Non sta nemmeno usando un dizionario, ma piuttosto una classe che implementa determinati metodi. – nearlymonolith

5

semplice esempio di ciò che è rotto, per interessi:

>>> count = 0 
>>> class BrokenHash(object): 
...  def __hash__(self): 
...    global count 
...    count += 1 
...    return count 
... 
...  def __eq__(self, other): 
...    return True 
... 
>>> foo = BrokenHash() 
>>> bar = BrokenHash() 
>>> foo is bar 
False 
>>> foo == bar 
True 
>>> baz = {bar:1} 
>>> foo in baz 
False 
>>> foo in baz.keys() 
True 
+2

Questo potrebbe essere il Python più patologico che abbia mai scritto ... – katrielalex

Problemi correlati