Widziałem przykład kodu, w którym hash
funkcja jest stosowana do krotki. W rezultacie zwraca ujemną liczbę całkowitą. Zastanawiam się, do czego służy ta funkcja? Google nie pomaga. Znalazłem stronę, która wyjaśnia, w jaki sposób obliczany jest hash, ale nie wyjaśnia, dlaczego potrzebujemy tej funkcji.
86
Odpowiedzi:
Skrót to liczba całkowita o stałym rozmiarze, która identyfikuje określoną wartość . Każda wartość musi mieć swój własny hash, więc dla tej samej wartości otrzymasz ten sam hash, nawet jeśli nie jest to ten sam obiekt.
>>> hash("Look at me!") 4343814758193556824 >>> f = "Look at me!" >>> hash(f) 4343814758193556824
Wartości skrótu należy tworzyć w taki sposób, aby wynikowe wartości były równomiernie rozłożone, aby zmniejszyć liczbę otrzymywanych kolizji. Kolizje skrótów występują, gdy dwie różne wartości mają ten sam skrót. Dlatego stosunkowo niewielkie zmiany często skutkują bardzo różnymi skrótami.
>>> hash("Look at me!!") 6941904779894686356
Liczby te są bardzo przydatne, ponieważ umożliwiają szybkie wyszukiwanie wartości w dużym zbiorze wartości. Dwa przykłady ich użycia to Python
set
idict
. W alist
, jeśli chcesz sprawdzić, czy wartość znajduje się na liście, za pomocąif x in values:
Python musi przejrzeć całą listę i porównaćx
z każdą wartością na liścievalues
. Może to zająć dużo czasulist
. W aset
, Python śledzi każdy hash, a kiedy piszeszif x in values:
, Python pobierze wartość skrótu dlax
, wyszuka ją w wewnętrznej strukturze, a następnie porówna tylkox
z wartościami, które mają taki sam skrót jakx
.Ta sama metodologia jest używana do wyszukiwania w słowniku. To sprawia, że wyszukiwanie do wewnątrz
set
i jestdict
bardzo szybkie, podczas gdy wyszukiwanie wlist
jest wolne. Oznacza to również, że możesz mieć obiekty niehashowalne w alist
, ale nie w aset
ani jako klucze w adict
. Typowym przykładem obiektów, których nie można haszować, jest każdy obiekt, który jest zmienny, co oznacza, że można zmienić jego wartość. Jeśli masz zmienny obiekt, nie powinien on być hashowany, ponieważ jego skrót zmieni się w czasie jego życia, co spowodowałoby wiele zamieszania, ponieważ obiekt może znaleźć się pod niewłaściwą wartością skrótu w słowniku.Zauważ, że hash wartości musi być taki sam tylko dla jednego uruchomienia Pythona. W Pythonie 3.3 będą się one faktycznie zmieniać przy każdym nowym uruchomieniu Pythona:
$ /opt/python33/bin/python3 Python 3.3.2 (default, Jun 17 2013, 17:49:21) [GCC 4.6.3] on linux Type "help", "copyright", "credits" or "license" for more information. >>> hash("foo") 1849024199686380661 >>> $ /opt/python33/bin/python3 Python 3.3.2 (default, Jun 17 2013, 17:49:21) [GCC 4.6.3] on linux Type "help", "copyright", "credits" or "license" for more information. >>> hash("foo") -7416743951976404299
Ma to na celu utrudnienie odgadnięcia, jaką wartość skrótu będzie miał dany ciąg, co jest ważną funkcją bezpieczeństwa w aplikacjach internetowych itp.
Dlatego wartości skrótu nie powinny być przechowywane na stałe. Jeśli chcesz używać wartości skrótu w sposób trwały, możesz przyjrzeć się bardziej "poważnym" typom skrótów , kryptograficznym funkcjom mieszania , które mogą być używane do tworzenia weryfikowalnych sum kontrolnych plików itp.
źródło
hash(-1) == hash(-2)
(runnin Python 2.7)hash(-1) == hash(-2)
istnieje do dziś. Na szczęście nie wpływa to negatywnie na słownik i zestawy wyszukiwań. Wszystkie inne liczby całkowitei
rozwiązują się same zahash(i)
wyjątkiem-1
.TL; DR:
Zapoznaj się ze słownikiem :
hash()
służy jako skrót do porównywania obiektów, obiekt jest uznawany za haszowalny, jeśli można go porównać z innymi obiektami. dlatego używamyhash()
. Jest również używany do uzyskiwania dostępudict
iset
elementów, które są implementowane jako tabele skrótów o zmiennym rozmiarze w CPythonie .Uwagi techniczne
hash()
funkcja jest o rząd wielkości (lub kilka) tańsza.Jeśli czytasz o implementacji słowników , używają one tablic mieszających, co oznacza, że wyprowadzenie klucza z obiektu jest kamieniem węgielnym do pobierania obiektów ze słowników w formacie
O(1)
. Jest to jednak bardzo zależne od odporności funkcji skrótu na kolizje . W rzeczywistości najgorszym przypadkiem jest umieszczenie elementu w słownikuO(n)
.W związku z tym zmienne obiekty zwykle nie podlegają hashowaniu. Właściwość hashable oznacza, że możesz użyć obiektu jako klucza. Jeśli wartość skrótu jest używana jako klucz, a zawartość tego samego obiektu ulegnie zmianie, to co powinna zwrócić funkcja skrótu? Czy to ten sam klucz czy inny? To zależy od tego, jak zdefiniujesz swoją funkcję skrótu.
Uczenie się na przykładzie:
Wyobraź sobie, że mamy tę klasę:
>>> class Person(object): ... def __init__(self, name, ssn, address): ... self.name = name ... self.ssn = ssn ... self.address = address ... def __hash__(self): ... return hash(self.ssn) ... def __eq__(self, other): ... return self.ssn == other.ssn ...
Uwaga: wszystko to opiera się na założeniu, że numer SSN nigdy nie zmienia się dla osoby (nawet nie wiem, gdzie faktycznie zweryfikować ten fakt z wiarygodnego źródła).
Mamy Boba:
>>> bob = Person('bob', '1111-222-333', None)
Bob idzie do sędziego, aby zmienić swoje imię:
>>> jim = Person('jim bo', '1111-222-333', 'sf bay area')
Oto, co wiemy:
>>> bob == jim True
Ale to są dwa różne obiekty z różną alokacją pamięci, tak jak dwa różne rekordy tej samej osoby:
>>> bob is jim False
Teraz przychodzi część, w której hash () jest przydatny:
>>> dmv_appointments = {} >>> dmv_appointments[bob] = 'tomorrow'
Zgadnij co:
>>> dmv_appointments[jim] #? 'tomorrow'
Z dwóch różnych rekordów możesz uzyskać dostęp do tych samych informacji. Teraz spróbuj tego:
>>> dmv_appointments[hash(jim)] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 9, in __eq__ AttributeError: 'int' object has no attribute 'ssn' >>> hash(jim) == hash(hash(jim)) True
Co się stało? To jest kolizja. Ponieważ przy
hash(jim) == hash(hash(jim))
okazji, które są obydwoma liczbami całkowitymi, musimy porównać dane wejściowe__getitem__
ze wszystkimi zderzającymi się elementami. Wbudowanyint
nie massn
atrybutu, więc wyłącza się.>>> del Person.__eq__ >>> dmv_appointments[bob] 'tomorrow' >>> dmv_appointments[jim] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: <__main__.Person object at 0x7f611bd37110>
W tym ostatnim przykładzie pokazuję, że nawet przy kolizji następuje porównanie, obiekty nie są już równe, co oznacza, że pomyślnie podnosi
KeyError
.źródło
hash()
jest liczbą całkowitą o stałym rozmiarze, która może powodować kolizję__eq__
w powyższym przykładzie. Czy jest wywoływana przez słownik, gdy próbuje porównać otrzymany klucz ze wszystkimi posiadanymi kluczami? Takie, żedel
w__eq__
metodzie w ostatnim przykładzie słownika nie ma nic na wezwanie do użycia w celu określenia równoważności klucza, które otrzymał z kluczami to ma?hash(jim)
.Person.__eq__
jest wywoływana, ponieważ istniejący klucz ma ten sam skrót, cohash(jim)
zapewnia, że jest to właściwy kluczPerson.__eq__
. To błądzi, ponieważ zakłada, żeother
, co jestint
, massn
atrybut. Gdybyhash(jim)
klucz nie istniał w słowniku__eq__
, nie zostałby wywołany. To wyjaśnia, kiedy wyszukiwanie klucza może mieć wartość O (n): kiedy wszystkie elementy mają ten sam hash,__eq__
należy je zastosować we wszystkich, np. W przypadku, gdy klucz nie istnieje.dmv_appointments[bob.ssn] = 'tomorrow'
, usuwając potrzebę zdefiniowania__hash__
metody? Rozumiem, że dodaje 4 znaki na każde spotkanie, które piszesz i czytasz, ale wydaje mi się to bardziej zrozumiałe.Dokumentacja Pythona dotycząca
hash()
stanu:Słowniki Pythona są implementowane jako tabele skrótów. Za każdym razem, gdy korzystasz ze słownika,
hash()
wywoływane są klucze, które przekazujesz w celu przypisania lub wyszukania.Ponadto w dokumentach
dict
typu :źródło
Hash jest używany przez słowniki i zestawy do szybkiego wyszukiwania obiektu. Dobrym punktem wyjścia jest artykuł Wikipedii na temat tabel skrótów .
źródło
Możesz użyć
Dictionary
typu danych w Pythonie. Jest bardzo podobny do skrótu - i obsługuje również zagnieżdżanie, podobnie jak w przypadku zagnieżdżonego skrótu.Przykład:
dict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'} dict['Age'] = 8; # update existing entry dict['School'] = "DPS School" # Add new entry print ("dict['Age']: ", dict['Age']) print ("dict['School']: ", dict['School'])
Aby uzyskać więcej informacji, zapoznaj się z tym samouczkiem dotyczącym typu danych w słowniku .
źródło