Warum vergleichen diese dtypes gleich, aber unterschiedlich?

8
%Vor%

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.

    
Neil G 09.02.2016, 13:45
quelle

4 Antworten

1

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:

%Vor%

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:

%Vor%

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:

%Vor%

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.

    
user2357112 17.07.2017, 01:26
quelle
4

Als tttthomasssss notes ist type (class) für np.float64 und d unterschiedlich. Sie sind verschiedene Arten von Dingen:

%Vor%

Typ type bedeutet (normalerweise), dass es eine Funktion ist, also kann es wie folgt verwendet werden:

%Vor%

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.

%Vor%

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

%Vor%

dtype ist keine Funktion oder Klasse:

%Vor%

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

%Vor%

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%     
hpaulj 09.02.2016 17:50
quelle
1

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:

%Vor%

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%     
tttthomasssss 09.02.2016 14:09
quelle
0

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:

%Vor%

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)

    
AndyG 09.02.2016 14:10
quelle

Tags und Links