2016-02-09 14 views
8
In [30]: import numpy as np 

In [31]: d = np.dtype(np.float64) 

In [32]: d 
Out[32]: dtype('float64') 

In [33]: d == np.float64 
Out[33]: True 

In [34]: hash(np.float64) 
Out[34]: -9223372036575774449 

In [35]: hash(d) 
Out[35]: 880835502155208439 

Perché questi dtypes confrontano uguale ma hash diversi?Perché questi dtypes confrontano uguale ma hash diversi?

Nota che Python fa promettere che:

L'unica proprietà richiesta è che gli oggetti che risultano uguali hanno lo stesso valore di hash ...

La mia soluzione per questo problema è quello di chiamare np.dtype su tutto, dopo di che i valori hash e i confronti sono coerenti.

+0

In realtà 'type (d) == type (np.float64)' è 'False'. E Python dice che solo "I valori numerici che si equivalgono hanno lo stesso valore di hash". – AndyG

+0

Se invece si dice 'd = np.float64', gli hash sono equivalenti. – AndyG

+0

@AndyG Certo, ma è perché sono lo stesso oggetto allora. Python promette che dovrebbero essere uguali se si confrontano uguali. –

risposta

1

Essi non dovrebbero comportarsi in questo modo, ma __eq__ e __hash__ per numpy.dtype oggetti sono rotti a livello di progettazione essenzialmente irreparabili. Tratterò pesantemente dai commenti di njsmith su uno dtype-related bug report per questa risposta.

np.float64 non è in realtà un dtype. È un tipo, nel senso comune del sistema di tipo Python. In particolare, se si recupera uno scalare da una matrice di dattilo float64, np.float64 è il tipo dello scalare risultante.

np.dtype(np.float64) è un dtype, un'istanza di numpy.dtype. i dtypes sono come NumPy registra la struttura dei contenuti di un array NumPy. Sono particolarmente importanti per structured arrays, che può avere dtypes molto complessi. Mentre i normali tipi di Python potevano aver riempito gran parte del ruolo dei dtype, la creazione di nuovi tipi al volo per i nuovi array strutturati sarebbe molto difficile, e probabilmente sarebbe stata impossibile nei giorni precedenti l'unificazione di classe-tipo.

numpy.dtype attrezzi __eq__ sostanzialmente in questo modo:

def __eq__(self, other): 
    if isinstance(other, numpy.dtype): 
     return regular_comparison(self, other) 
    return self == numpy.dtype(other) 

che è abbastanza rotto. Tra gli altri problemi, non è transitiva, solleva TypeError quando deve restituire NotImplemented, e la sua uscita è davvero bizzarro, a volte a causa di come funziona DTYPE coercizione:

>>> x = numpy.dtype(numpy.float64) 
>>> x == None 
True 

numpy.dtype.__hash__ non è niente di meglio.Non fa alcun tentativo di essere coerente con i metodi __hash__ di tutti gli altri tipi numpy.dtype.__eq__ accetta (e con così tanti tipi incompatibili da gestire, come potrebbe?). Diamine, non dovrebbe nemmeno esistere, perché gli oggetti dtype sono mutevoli! Non solo modificabili come moduli o oggetti file, dove va bene perché __eq__ e __hash__ funzionano per identità. oggetti DTYPE sono mutabili in modi che effettivamente cambiare il loro valore di hash:

>>> x = numpy.dtype([('f1', float)]) 
>>> hash(x) 
-405377605 
>>> x.names = ['f2'] 
>>> hash(x) 
1908240630 

Quando si tenta di confrontare d == np.float64, d.__eq__ costruisce un DTYPE fuori np.float64 e scopre che d == np.dtype(np.float64) è vero. Quando si prendono gli hash, tuttavia, np.float64 utilizza l'hash regolare (basato sull'identità) per gli oggetti tipo e d utilizza l'hash per gli oggetti dtype. Normalmente, gli oggetti uguali di tipi diversi dovrebbero avere gli stessi hash, ma l'implementazione del dtype non gli interessa.

Sfortunatamente, è impossibile risolvere i problemi con dtype __eq__ e __hash__ senza interrompere le API su cui le persone fanno affidamento. Le persone contano su cose come x.dtype == 'float64' o x.dtype == np.float64, e la correzione dei dtype lo spezzerebbe.

+0

Grazie per la risposta. Tuttavia, non è impossibile risolvere questi problemi. Possono deprecare alcune delle scelte progettuali sbagliate, e dopo alcuni anni sostituire il meccanismo. Ad esempio, supportare 'x.dtype == 'float64'' non è stata una buona idea. –

1

non sono la stessa cosa, mentre np.float64 è un type, d è un'istanza di numpy.dtype, di conseguenza, essi hash per valori diversi, ma tutte le istanze del d creati allo stesso modo sarà hash allo stesso valore perché sono identici (il che ovviamente non significa necessariamente che puntino alla stessa posizione di memoria).

Edit:

Dato il tuo codice di cui sopra è possibile provare la seguente:

In [72]: type(d) 
Out[72]: numpy.dtype 

In [74]: type(np.float64) 
Out[74]: type 

che mostra che i due sono di tipo diverso e, quindi, sarà di hash per valori diversi. Mostrando che le diverse istanze di numpy.dtype possono essere visualizzati dal seguente esempio:

In [77]: import copy 
In [78]: dd = copy.deepcopy(d) # Try copying 

In [79]: dd 
Out[79]: dtype('float64') 

In [80]: hash(dd) 
Out[80]: -6584369718629170405 

In [81]: hash(d) # original d 
Out[81]: -6584369718629170405 

In [82]: ddd = np.dtype(np.float64) # new instance 
In [83]: hash(ddd) 
Out[83]: -6584369718629170405 

# If using CPython, id returns the address in memory (see: https://docs.python.org/3/library/functions.html#id) 
In [84]: id(ddd) 
Out[84]: 4376165768 

In [85]: id(dd) 
Out[85]: 4459249168 

In [86]: id(d) 
Out[86]: 4376165768 

Il suo bello vedere che ddd (l'istanza ha creato allo stesso modo di d), e d si condividono lo stesso oggetto in memoria, ma dd (l'oggetto copiato) utilizza un indirizzo diverso.

L'uguaglianza controlli valutare come ci si aspetterebbe, dato gli hash di cui sopra:

In [87]: dd == np.float64 
Out[87]: True 
In [88]: d == np.float64 
Out[88]: True 
In [89]: ddd == np.float64 
Out[89]: True 
In [90]: d == dd 
Out[90]: True 
In [91]: d == ddd 
Out[91]: True 
In [92]: dd == ddd 
Out[92]: True 
+0

Questa sembra la risposta giusta, ma puoi espanderla un po 'per favore? –

+0

@NeilG Ho aggiornato la mia risposta, spero che sia ciò che stavi cercando;) – tttthomasssss

0

è perché si sta hashing un type contro un oggetto dtype.

Sebbene i valori risultano uguali (come prove da d == np.float64, loro tipi sono differenti:

print type(d) 
print type(np.float64) 

Produce

< tipo 'numpy.dtype' >

< tipo 'tipo '>

Secondo il Python docs:

hash (oggetto)

Ritorna il valore hash dell'oggetto (se ne ha uno).I valori hash sono numeri interi. Sono utilizzati per confrontare rapidamente le chiavi del dizionario durante una ricerca nel dizionario. I valori numerici che confrontano uguali hanno lo stesso valore di hash (anche se sono di tipi diversi, come nel caso di 1 e 1.0).

E poiché un dtype non è un tipo numerico, non v'è alcuna garanzia che tale e oggetto provocherà lo stesso hash come type che confronta uguali.


EDIT: Dal Python 3.5 docs:

object.__hash__(self)

Chiamato da funzione hash built-in() e per le operazioni sui membri di collezioni hash tra cui set, frozenset e dict. hash() dovrebbe restituire un numero intero. L'unica proprietà richiesta è che gli oggetti con uguale confronto abbiano lo stesso valore di hash; si consiglia di mescolare in qualche modo (ad esempio usando esclusivi o) i valori hash per i componenti dell'oggetto che svolgono anche una parte nel confronto degli oggetti.

che sembra implicare che hash(d) == hash(np.float64) dovrebbe restituire True nel tuo caso.

Ho notato che c'è una nota subito dopo afferma:

hash() tronca il valore restituito da di un oggetto personalizzato hash() per la dimensione di un Py_ssize_t. Si tratta in genere di 8 byte su build a 64 bit e 4 byte su build a 32 bit.

Tuttavia, non sono stato in grado di determinare che la dimensione degli oggetti restituiti dalle funzioni hash fosse in realtà diversa; appaiono la stessa (io ho usato sys.getsizeof)

+0

Si prega di vedere la mia domanda aggiornata. Ho citato un'altra parte dei documenti. –

4

Come tttthomasssss note, il type (classe) per np.float64 e d sono diversi. Essi sono diversi tipi di cose:

In [435]: type(np.float64) 
Out[435]: type 

Tipo type mezzi (di solito) che è una funzione, in modo che possa essere utilizzato come:

In [436]: np.float64(0) 
Out[436]: 0.0 

In [437]: type(_) 
Out[437]: numpy.float64 

creazione di un oggetto numerico. In realtà sembra più una definizione di classe. Ma dal momento che numpy utilizza un sacco di codice compilato e il suo ndarray utilizza il proprio __new__, non sarei sorpreso se si trova a cavallo della linea.

In [438]: np.float64.__hash__?? 
Type:  wrapper_descriptor 
String Form:<slot wrapper '__hash__' of 'float' objects> 
Docstring: x.__hash__() <==> hash(x) 

pensavo questo sarebbe il hash(np.float64), ma potrebbe in realtà essere l'hash per un oggetto di quel tipo, per esempio hash(np.float64(0)). In tal caso, hash(np.float64) utilizza solo il metodo predefinito type.__hash__.

Passando al dtype:

In [439]: d=np.dtype(np.float64) 

In [440]: type(d) 
Out[440]: numpy.dtype 

d non è una funzione o di classe:

In [441]: d(0) 
... 
TypeError: 'numpy.dtype' object is not callable 

In [442]: d.__hash__?? 
Type:  method-wrapper 
String Form:<method-wrapper '__hash__' of numpy.dtype object at 0xb60f8a60> 
Docstring: x.__hash__() <==> hash(x) 

Sembra np.dtype non definisce alcun metodo speciale __hash__, solo eredita da object.

Inoltre illustra la differenza tra float64 e d, guarda la pila di ereditarietà di classe

In [443]: np.float64.__mro__ 
Out[443]: 
(numpy.float64, 
numpy.floating, 
numpy.inexact, 
numpy.number, 
numpy.generic, 
float, 
object) 

In [444]: d.__mro__ 
... 
AttributeError: 'numpy.dtype' object has no attribute '__mro__' 

In [445]: np.dtype.__mro__ 
Out[445]: (numpy.dtype, object) 

Quindi np.float64 non definisce un hash o, semplicemente eredita da float. d non ha un __mro__ perché è un oggetto, non una classe.

numpy ha abbastanza codice compilato e una lunga storia a parte, che non si può contare sulla documentazione Python sempre valida.

np.dtype e np.float64 hanno evidentemente __eq__ metodi che consentono loro di essere confrontati tra loro, ma numpy sviluppatori non ha messo alcun sforzo nel fare in modo che i metodi __hash__ conformi. Molto probabilmente perché non devono essere utilizzati come chiave del dizionario.

codice come non ho mai visto:

In [453]: dd={np.float64:12,d:34} 

In [454]: dd 
Out[454]: {dtype('float64'): 34, numpy.float64: 12} 

In [455]: dd[np.float64] 
Out[455]: 12 

In [456]: dd[d] 
Out[456]: 34 
+0

Questo è esattamente quello che stavo cercando. Grazie! Gli oggetti dtype –

+0

hanno effettivamente un'implementazione '__hash__' personalizzata, ma è difficile da individuare, perché il campo' tp_hash' di 'PyTypeObject' è impostato in una [posizione davvero strana] (https://github.com/numpy/ NumPy/blob/d4eaa2c01801ca2ce46b0c8b345367a54c8dde4b/NumPy/core/src/multiarray/multiarraymodule.C# L4599). Puoi vedere l'implementazione [qui] (https://github.com/numpy/numpy/blob/c11628abd820a1f44b052ea87af810f8f00cf2e4/numpy/core/src/multiarray/hashdescr.c#L297). – user2357112

Problemi correlati