I tipi che definiscono `__eq__` non sono lavabili?
-
05-07-2019 - |
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?
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 nonid(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__
? :)