2016-02-09 20 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 

Dlaczego te dtypes są jednakowe, ale zawierają różne?Dlaczego te dtypes są równe, ale różne?

Zauważ, że Python nie obiecuję, że:

Jedyna wymagana właściwość jest, że przedmioty, które porównują równa mieć sama wartość hash ...

Moja obejście tego problemu jest zadzwonić np.dtype na wszystko, po czym wartości mieszania i porównania są spójne.

+0

Właściwie "typ (d) == typ (np.float64)" to "False". A Python mówi, że tylko "Wartości liczbowe, które są równe, mają tę samą wartość skrótu". – AndyG

+0

Jeśli zamiast tego powiesz 'd = np.float64', to skróty są równoważne. – AndyG

+0

@AndyG Pewnie, ale to dlatego, że są wtedy tym samym obiektem. Python obiecuje, że powinny mieć wartość równą, jeśli będą porównywać. –

Odpowiedz

1

one nie powinno zachowywać się w ten sposób, ale __eq____hash__ dla numpy.dtype i obiekty są podzielone na zasadzie unfixable poziomie projektowania. Będę mocno wyciągać z komentarzy njsmitha o dtype-related bug report dla tej odpowiedzi.

np.float64 w rzeczywistości nie jest typem dtype. Jest to typ, w zwykłym znaczeniu systemu typu Python. W szczególności, jeśli pobierzesz skalar z tablicy float64 dtype, np.float64 jest rodzajem wynikowego skalara.

np.dtype(np.float64) to typ dtype, instancja numpy.dtype. dtypes to sposób, w jaki NumPy zapisuje strukturę zawartości tablicy NumPy. Są szczególnie ważne dla structured arrays, które mogą mieć bardzo złożone dtypy. Podczas gdy zwykłe typy Pythona mogły pełnić znaczną rolę dtypów, tworzenie nowych typów w locie dla nowych uporządkowanych tablic byłoby wysoce niezręczne i prawdopodobnie byłoby to niemożliwe w czasach przed ujednoliceniem klasy typów.

numpy.dtype narzędzia __eq__ w zasadzie tak:

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

który jest całkiem zepsuty. Wśród innych problemów, to nie jest przechodnia, podnosi TypeError kiedy powinien on powrócić NotImplemented, a jego produkcja jest naprawdę dziwne czasami z powodu, jak działa dtype przymus:

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

numpy.dtype.__hash__ nie jest lepiej.Nie stara się być zgodna z metodami akceptowanymi przez wszystkie inne typy (z tak wieloma niekompatybilnymi typami, jak można to zrobić?). Heck, to nie powinno nawet istnieć, ponieważ obiekty dtype są zmienne! Nie tylko zmienne, takie jak moduły lub obiekty plików, gdzie jest to w porządku, ponieważ __eq__ i __hash__ działają według tożsamości. obiekty dtype są zmienne w sposób, który spowoduje zmiany ich wartości hash:

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

Podczas próby porównać d == np.float64, d.__eq__ buduje dtype z np.float64 i stwierdza, że ​​d == np.dtype(np.float64) jest prawda. Jednak gdy używasz skrótów, np.float64 używa zwykłego (opartego na tożsamości) skrótu dla obiektów typu, a d używa skrótu dla obiektów typu dtype. Zwykle jednakowe obiekty różnych typów powinny mieć równe wartości skrótów, ale implementacja typu Dtype tego nie obchodzi.

Niestety, niemożliwe jest naprawienie problemów z dtype __eq__ i __hash__ bez łamania API, na których ludzie polegają. Ludzie liczą na takie rzeczy, jak x.dtype == 'float64' lub x.dtype == np.float64, a naprawianie dtypów mogłoby to zepsuć.

+0

Dzięki za odpowiedź. Jednak nie jest to niemożliwe, aby rozwiązać te problemy. Mogą zdezaktualizować niektóre z kiepskich wyborów projektowych, a następnie po kilku latach wymienić mechanizm. Np. Obsługa 'x.dtype == 'float64'' nie była dobrym pomysłem. –

1

one nie są tym samym, podczas np.float64 jest type, d jest instancją z numpy.dtype, stąd ich mieszania się różnych wartości, ale wszystkie przypadki d tworzone w ten sam sposób będzie hash do tej samej wartości, ponieważ są identyczne (co oczywiście nie musi oznaczać, że wskazują na to samo miejsce w pamięci).

Edit:

Biorąc pod uwagę powyższy kod można spróbować wykonać następujące czynności:

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

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

który pokazuje, że dwa są różnego typu, a tym samym będzie hash do różnych wartościach. Pokazuje, że różne instancje numpy.dtype mogą być pokazane w poniższym przykładzie:

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 

Jego miło zobaczyć, że ddd (instancja stworzony w taki sam sposób jak d) i d sama dzielić ten sam obiekt w pamięci, ale dd (skopiowany obiekt) używa innego adresu.

Równość sprawdza ocenić, jak można oczekiwać, biorąc pod uwagę powyższe skróty:

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

To wygląda na właściwą odpowiedź, ale czy możesz ją nieco rozszerzyć? –

+0

@NeilG Zaktualizowałem swoją odpowiedź, mam nadzieję, że jej to, czego szukałeś;) – tttthomasssss

0

To dlatego jesteś mieszania się type przeciwko dtype obiektu.

Choć parametry porównać równe (jako dowody przez d == np.float64, ich typy są różne:

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

Produkuje

< typu 'numpy.dtype' >

< typ „typ '>

Według Python docs:

hash (object)

Zwraca wartość hash obiektu (jeśli posiada).Wartości skrótu są liczbami całkowitymi. Służą do szybkiego porównywania kluczy słownikowych podczas wyszukiwania słownika. Wartości liczbowe, które są równe, mają tę samą wartość skrótu (nawet jeśli są różnych typów, tak jak w przypadku 1 i 1.0).

A ponieważ dtype nie jest typ numeryczny, nie ma gwarancji, że taki obiekt i spowoduje tym samym hash jako type który porównuje równe.


EDIT: Z Python 3.5 docs:

object.__hash__(self)

Wywoływana przez wbudowaną funkcję hash() i dla operacji na członków hashed kolekcjach w tym zestawie, frozenset i dict. hash() powinien zwrócić liczbę całkowitą. Jedyną wymaganą właściwością jest to, że obiekty, które są równe, mają tę samą wartość skrótu; zaleca się jakoś połączyć ze sobą (np. używając wyłączności lub) wartości mieszania dla składników obiektu, które również odgrywają rolę w porównaniu z obiektami.

Który sugeruje, że hash(d) == hash(np.float64) powinien zwrócić True w twoim przypadku.

Zauważyłem, że istnieje uwaga zaraz potem stwierdza:

hash() obcina wartość zwracaną z obiektu za zwyczaj hash() metody do wielkości Py_ssize_t. Zwykle jest to 8 bajtów w kompilacjach 64-bitowych i 4 bajtach w kompilacjach 32-bitowych.

Jednak nie byłem w stanie stwierdzić, że rozmiar obiektów zwróconych z funkcji skrótu był w rzeczywistości inny; oni wyglądają tak samo (użyłem sys.getsizeof)

+0

Proszę zobaczyć moje zaktualizowane pytanie. Zacytowałem kolejną część dokumentów. –

4

Jak tttthomasssss informacji dodatkowej (klasa) type dla np.float64 i d są różne. Są różne rodzaje rzeczy:

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

Rodzaj type środki (zazwyczaj), że jest to funkcja, dzięki czemu może być używany jako:

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

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

tworzenia obiektu numerycznej. Właściwie to wygląda bardziej jak definicja klasy. Ale od numpy używa dużo skompilowanego kodu, a jego ndarray używa własnego __new__, nie byłbym zaskoczony, gdyby znalazł się na linii.

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

Myślałam ten miałby hash(np.float64), ale może to być rzeczywiście hash dla obiektu tego typu, na przykład hash(np.float64(0)). W takim przypadku hash(np.float64) po prostu używa domyślnej metody type.__hash__.

Przechodząc do dtype:

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

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

d nie jest funkcją lub klasa:

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) 

Wygląda np.dtype nie określa żadnej specjalnej metody __hash__, to po prostu dziedziczy object.

Dalsze ilustrujący różnicę między float64 i d, spojrzeć na stosie klasy spadków

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) 

Więc np.float64 nie definiuje mieszania albo, po prostu dziedziczy float. d nie ma wartości __mro__, ponieważ jest obiektem, a nie klasą.

numpy ma wystarczająco dużo skompilowanego kodu i długą własną historię, że nie można liczyć na to, że dokumentacja Pythona będzie zawsze stosowana.

np.dtype i widocznie mają __eq__np.float64 metod, które pozwolą im być ze sobą porównywane, ale numpy deweloperom nie wkładać żadnego wysiłku w upewniając się, że metody __hash__ zgodne. Najprawdopodobniej dlatego, że nie muszą używać jako klucza słownika.

Nigdy nie widziałem jak kod:

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

Dokładnie tego szukałem. Dziękuję Ci! –

+0

Obiekty dtype rzeczywiście mają niestandardową implementację '__hash__', ale trudno ją zauważyć, ponieważ pole' tp_hash' obiektu 'PyTypeObject' jest ustawione w [naprawdę dziwnej lokalizacji] (https://github.com/numpy/ numpy/blob/d4eaa2c01801ca2ce46b0c8b345367a54c8dde4b/numpy/core/src/multiarray/multiarraymodule.C# L4599). Możesz zobaczyć implementację [tutaj] (https://github.com/numpy/numpy/blob/c11628abd820a1f44b052ea87af810f8f00cf2e4/numpy/core/src/multiarray/hashdescr.c#L297). – user2357112

Powiązane problemy