Warum vergleichen diese dtypes gleiche, aber unterschiedliche Hashes?
Beachten Sie, dass Python das verspricht:
Die einzige erforderliche Eigenschaft ist, dass Objekte, die gleiche Werte haben, vorhanden sind der gleiche Hash-Wert ...
Meine Problemumgehung für dieses Problem besteht darin, np.dtype
für alles aufzurufen, nach dem Hashwerte und Vergleiche konsistent sind.
Sie sollten nicht so verhalten, aber __eq__
und __hash__
für numpy.dtype
Objekte sind auf einer im Wesentlichen nicht fixierbaren Design-Ebene gebrochen. Ich werde stark von njsmiths Kommentaren auf einen dtype-bezogenen Fehlerbericht für diese Antwort ziehen.
np.float64
ist eigentlich kein dtype. Es ist ein Typ im üblichen Sinne des Python-Typ-Systems. Insbesondere wenn Sie einen Skalar aus einem Array von float64 dtype abrufen, ist np.float64
der Typ des resultierenden Skalars.
np.dtype(np.float64)
ist ein dtype, eine Instanz von numpy.dtype
. dtypes ist, wie NumPy die Struktur des Inhalts eines NumPy-Arrays aufzeichnet. Sie sind besonders wichtig für strukturierte Arrays , die sehr komplexe dtypes haben können. Während gewöhnliche Python-Typen einen großen Teil der Rolle von dtypes erfüllen konnten, wäre das Erstellen neuer Typen für neue strukturierte Arrays im Handumdrehen sehr umständlich und wäre wahrscheinlich in den Tagen vor der Typklassen-Vereinheitlichung unmöglich gewesen.
numpy.dtype
implementiert __eq__
grundsätzlich wie folgt:
was ziemlich kaputt ist. Unter anderen Problemen ist es nicht transitiv, es hebt TypeError
an, wenn es NotImplemented
zurückgeben soll, und seine Ausgabe ist manchmal wirklich bizarr, weil dtype coercion funktioniert:
numpy.dtype.__hash__
ist nicht besser. Es macht keinen Versuch, mit den __hash__
-Methoden aller anderen Typen konsistent zu sein, die numpy.dtype.__eq__
akzeptiert (und mit so vielen inkompatiblen Typen zu tun hat, wie könnte es sein?). Verdammt, es sollte nicht einmal existieren, denn dtype-Objekte sind veränderbar! Nicht nur änderbar wie Module oder Dateiobjekte, wo es in Ordnung ist, weil __eq__
und __hash__
nach Identität arbeiten. dtype-Objekte sind auf eine Weise veränderbar, die ihren Hash-Wert tatsächlich ändert:
Wenn Sie versuchen, d == np.float64
zu vergleichen, erstellt d.__eq__
einen dtype aus np.float64
und stellt fest, dass d == np.dtype(np.float64)
True ist. Wenn Sie jedoch ihre Hashwerte verwenden, verwendet np.float64
den regulären (identitätsbasierten) Hash für Typobjekte und d
den Hash für dtype-Objekte. Normalerweise sollten gleiche Objekte unterschiedlicher Typen gleiche Hashes haben, aber die dtype-Implementierung kümmert sich nicht darum.
Leider ist es unmöglich, die Probleme mit dtype __eq__
und __hash__
zu beheben, ohne die APIs zu brechen, auf die sich die Leute verlassen. Leute zählen auf Dinge wie x.dtype == 'float64'
oder x.dtype == np.float64
, und das Reparieren von dtypes würde das brechen.
Als tttthomasssss
notes ist type
(class) für np.float64
und d
unterschiedlich. Sie sind verschiedene Arten von Dingen:
Typ type
bedeutet (normalerweise), dass es eine Funktion ist, also kann es wie folgt verwendet werden:
Erstellen eines numerischen Objekts. Eigentlich sieht das eher nach einer Klassendefinition aus. Aber da numpy
viel kompilierten Code verwendet und ndarray
sein eigenes __new__
verwendet, würde ich nicht überrascht sein, wenn es die Zeile überspannt.
Ich dachte, dies wäre hash(np.float64)
, aber es könnte tatsächlich der Hash für ein Objekt dieses Typs sein, z.B. %Code%. In diesem Fall verwendet hash(np.float64(0))
nur die Standardmethode hash(np.float64)
.
Weiter zum type.__hash__
:
dtype
ist keine Funktion oder Klasse:
Sieht so aus, als ob d
keine spezielle np.dtype
-Methode definiert, sondern nur von __hash__
.
Schauen Sie sich den Unterschied zwischen object
und float64
genauer an und schauen Sie sich den Stapel der Klassenvererbung an
So d
definiert auch keinen Hash, er erbt nur von np.float64
. float
hat kein d
, weil es ein Objekt und keine Klasse ist.
__mro__
hat genug kompilierten Code und eine lange eigene Geschichte, dass Sie nicht damit rechnen können, dass die Python-Dokumentation immer angewendet wird.
numpy
und np.dtype
haben offensichtlich np.float64
-Methoden, mit denen sie miteinander verglichen werden können, aber __eq__
-Entwickler haben sich nicht darum gekümmert sicherzustellen, dass die numpy
-Methoden übereinstimmen. Höchstwahrscheinlich, weil sie keinen der beiden Schlüssel als Wörterbuch verwenden müssen.
Ich habe noch nie Code wie:
gesehen %Vor% Sie sind nicht das Gleiche, während np.float64
eine type
ist, d
eine Instanz von numpy.dtype
ist, also Hashwerte für verschiedene Werte, aber alle Instanzen von d
, das auf die gleiche Weise erzeugt wird, wird auf den gleichen Wert gerastert, weil sie identisch sind (was natürlich nicht bedeutet, dass sie auf den gleichen Speicherort verweisen).
Bearbeiten:
Wenn Sie Ihren obigen Code eingegeben haben, können Sie Folgendes versuchen:
%Vor% zeigt Ihnen, dass die beiden von unterschiedlichem Typ sind und daher auf verschiedene Werte hascht. Es wird gezeigt, dass verschiedene Instanzen von numpy.dtype
im folgenden Beispiel gezeigt werden können:
Es ist schön zu sehen, dass ddd
(die Instanz genauso wie d
erstellt wurde) und d
selbst teilen dasselbe Objekt im Speicher, aber dd
(das kopierte Objekt) verwendet eine andere Adresse.
Die Gleichheitsprüfungen werden so bewertet, wie Sie es angesichts der obigen Hashes erwarten würden:
%Vor% Dies liegt daran, dass Sie ein type
gegen ein dtype
Objekt hashen.
Obwohl die Werte gleich sind (als Beweise für d == np.float64
, unterscheiden sich ihre Typen:
Produziert
& lt; geben Sie 'numpy.dtype' & gt;
ein& lt; geben Sie "type" & gt;
ein
Entsprechend den Python-Dokumenten :
hash
(Objekt)Gibt den Hash-Wert des Objekts (falls vorhanden) zurück. Hash-Werte sind Ganzzahlen. Sie werden verwendet, um Wörterbuchschlüssel während einer Wörterbuchsuche schnell zu vergleichen. Numerische Werte, die mit "Gleich" verglichen werden, haben denselben Hash-Wert (auch wenn sie unterschiedliche Typen haben, wie dies bei 1 und 1.0 der Fall ist).
Und da ein dtype
kein numerischer Typ ist, gibt es keine Garantie, dass diese und das Objekt den gleichen Hashwert ergeben wie ein type
, der gleich ist.
BEARBEITEN: Aus den Python 3.5-Dokumenten :
object.__hash__(self)
Wird von der integrierten Funktion hash () aufgerufen und für Operationen an Mitgliedern von Hash-Sammlungen, einschließlich set, frozenset und dict. hash () sollte eine ganze Zahl zurückgeben. Die einzige erforderliche Eigenschaft besteht darin, dass Objekte, die gleich sind, denselben Hash-Wert haben; Es wird empfohlen, die Hash-Werte für die Komponenten des Objekts, die auch beim Vergleich von Objekten eine Rolle spielen, irgendwie miteinander zu vermischen (z. B. mit exklusiven oder).
Dies scheint zu implizieren, dass hash(d) == hash(np.float64)
in Ihrem Fall True
zurückgeben sollte.
Ich habe bemerkt, dass unmittelbar danach eine Notiz steht:
hash()
schneidet den von der benutzerdefinierten Methode hash () eines Objekts zurückgegebenen Wert auf die Größe eines Py_ssize_t ab. Dies ist in der Regel 8 Byte bei 64-Bit-Builds und 4 Byte bei 32-Bit-Builds.
Allerdings konnte ich nicht feststellen, dass die Größe der von den Hash-Funktionen zurückgegebenen Objekte tatsächlich verschieden war; sie erscheinen gleich (ich habe sys.getsizeof
verwendet)