Domanda

Ho avuto uno strano bug durante il porting di una funzione sul fork di Python 3.1 del mio programma. L'ho ridotto alla seguente ipotesi:

Contrariamente a Python 2.x, in Python 3.x se un oggetto ha un metodo __eq__ è automaticamente non lavabile.

È vero?

Ecco cosa succede in Python 3.1:

>>> class O(object):
...     def __eq__(self, other):
...         return 'whatever'
...
>>> o = O()
>>> d = {o: 0}
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    d = {o: 0}
TypeError: unhashable type: 'O'

La domanda di follow-up è: come posso risolvere il mio problema personale? Ho un oggetto ChangeTracker che memorizza un WeakKeyDictionary che punta a più oggetti, dando a ciascuno il valore della loro discarica di sottaceti in un determinato momento nel passato. Ogni volta che un oggetto esistente viene archiviato, il tracker modifiche indica se il suo nuovo pickle è identico a quello precedente, indicando quindi se l'oggetto è cambiato nel frattempo. Il problema è che ora non riesco nemmeno a verificare se l'oggetto specificato si trova nella libreria, perché lo fa sollevare un'eccezione in quanto l'oggetto non è lavabile. (Perché ha un metodo <=>.) Come posso aggirare questo?

È stato utile?

Soluzione

Sì, se si definisce __eq__, il valore predefinito __hash__ (ovvero, l'hashing dell'indirizzo dell'oggetto in memoria) scompare. Questo è importante perché l'hashing deve essere coerente con l'uguaglianza: gli oggetti uguali devono avere lo stesso hash.

La soluzione è semplice: basta definire <=> insieme a <=>.

Altri suggerimenti

Questo paragrafo di http://docs.python.org/3.1 /reference/datamodel.html#object. hash

  

Se una classe che sostituisce __eq__()   deve mantenere l'attuazione di   __hash__() da una classe genitore, deve essere detto all'interprete   esplicitamente impostando __hash__ = <ParentClass>.__hash__. Altrimenti il   l'eredità di __hash__ sarà   bloccato, proprio come se <=> fosse stato   impostato esplicitamente su Nessuno.

Controlla il manuale di Python 3 su object.__hash__ :

  

Se una classe non definisce un metodo __eq__(), non dovrebbe nemmeno definire un'operazione __hash__(); se definisce __hash__(self) ma non id(self), le sue istanze non saranno utilizzabili come elementi in raccolte hash.

L'enfasi è mia.

Se vuoi essere pigro, sembra che puoi semplicemente definire x.__hash__() per tornare id(x):

  

Le classi definite dall'utente hanno <=> e <=> metodi per impostazione predefinita; con essi, tutti gli oggetti si confrontano in modo ineguale (tranne con se stessi) e <=> restituisce <=>.

Non sono un esperto di Python, ma non avrebbe senso che, quando definisci un metodo eq, devi anche definire un metodo hash (che calcola il valore hash per un oggetto) Altrimenti, il meccanismo di hashing non saprebbe se ha colpito lo stesso oggetto o un oggetto diverso con lo stesso valore di hash. In realtà è il contrario, probabilmente finirebbe per calcolare diversi valori di hash per oggetti considerati uguali dal tuo metodo __eq__.

Non ho idea di come si chiami quella funzione hash, forse __hash__? :)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top