Що робить хеш у python?


86

Я бачив приклад коду, де hashфункція застосовується до кортежу. В результаті він повертає від'ємне ціле число. Цікаво, що робить ця функція? Google не допомагає. Я знайшов сторінку, яка пояснює, як обчислюється хеш, але вона не пояснює, навіщо нам потрібна ця функція.



перейдіть за цим посиланням (офіційна документація). У ній вказано все. перейти за посиланням !
tailor_raj

2
Мені подобається, що питання полягає не в повторенні "що це", а в тому, "навіщо це нам потрібно".
dnozay

офіційне посилання дуже заплутане
Rasmi Ranjan Nayak

Відповіді:


148

Хеш - це ціле число фіксованого розміру, яке ідентифікує певне значення . Кожне значення має мати власний хеш, тому для одного і того ж значення ви отримаєте той самий хеш, навіть якщо це не той самий об'єкт.

>>> hash("Look at me!")
4343814758193556824
>>> f = "Look at me!"
>>> hash(f)
4343814758193556824

Значення хешу потрібно створювати таким чином, щоб отримані значення рівномірно розподілялися, щоб зменшити кількість отриманих колізійних зіткнень. Хеш-зіткнення - це коли два різних значення мають однаковий хеш. Тому порівняно невеликі зміни часто призводять до дуже різних хешів.

>>> hash("Look at me!!")
6941904779894686356

Ці цифри дуже корисні, оскільки дозволяють швидко шукати значення у великій колекції значень. Два приклади їх використання - Python setта dict. В list, якщо ви хочете , щоб перевірити , чи є значення в списку, з if x in values:, Python повинен пройти весь список і порівняйте xз кожним значенням в списку values. Це може зайняти багато часу list. У a set, Python відстежує кожен хеш, і коли ви вводите if x in values:, Python отримає хеш-значення для x, шукайте це у внутрішній структурі, а потім порівнюйте лише xзі значеннями, які мають той самий хеш, що і x.

Ця ж методологія використовується для пошуку словників. Це робить пошук setі dictдуже швидким, тоді як пошук listвідбувається повільно. Це також означає, що ви можете мати незмішувані об'єкти в a list, але не в a setабо як ключі в a dict. Типовим прикладом негешових об'єктів є будь-який об'єкт, який можна змінювати, тобто ви можете змінити його значення. Якщо у вас є змінний об'єкт, його не слід хешувати, оскільки його хеш буде змінюватися протягом усього свого життя, що може спричинити багато плутанини, оскільки об'єкт може потрапити під неправильне значення хешу в словнику.

Зверніть увагу, що хеш значення повинен бути однаковим лише для одного запуску Python. У Python 3.3 вони фактично змінюватимуться для кожного нового запуску Python:

$ /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

Це зробити важче здогадатися, яке хеш-значення матиме певний рядок, що є важливою функцією безпеки для веб-додатків тощо.

Тому значення хешу не слід зберігати постійно. Якщо вам потрібно постійно використовувати хеш-значення, ви можете поглянути на більш "серйозні" типи хеш-функцій , криптографічні хеш-функції , які можна використовувати для створення перевіряваних контрольних сум файлів тощо.


11
Що стосується потенційних хеш-зіткнень: hash(-1) == hash(-2)(runnin Python 2.7)
Маттіас

2
У мене запущений Python 3.6.1, і зіткнення існує.
The_Martian

hash(-1) == hash(-2)існує й сьогодні. На щастя, це не впливає негативно на пошук у словнику та наборах. Всі інші цілі числа iвирішують самі, hash(i)крім -1.
Кріс Конлан,

35

TL; DR:

Будь ласка, зверніться до глосарію : hash()використовується як ярлик для порівняння об’єктів, об’єкт вважається хешируемым, якщо його можна порівняти з іншими об’єктами. саме тому ми використовуємо hash(). Він також використовується для доступу dictта setелементів, які реалізовані як змінні хеш-таблиці в CPython .

Технічні міркування

  • зазвичай порівняння об'єктів (які можуть включати кілька рівнів рекурсії) є дорогим.
  • переважно, hash()функція є на порядок (або на кілька) дешевшою.
  • порівняти два хеші простіше, ніж порівняти два об'єкти, саме тут є ярлик.

Якщо ви прочитаєте про те, як реалізуються словники , вони використовують хеш-таблиці, що означає, що отримання ключа від об'єкта є наріжним каменем для отримання об'єктів у словниках в O(1). Однак це дуже залежить від вашої хеш-функції, щоб бути стійким до зіткнень . Найгірший випадок для отримання елемента в словнику фактично O(n).

Зверніть увагу, що змінні об'єкти, як правило, не можна розмити. Властивість hashable означає, що ви можете використовувати об'єкт як ключ. Якщо хеш-значення використовується як ключ і зміст того самого об’єкта змінюється, то що повинна повертати хеш-функція? Це той самий ключ чи інший? Це залежить від того, як ви визначаєте свою хеш-функцію.

Навчання на прикладі:

Уявіть, у нас такий клас:

>>> 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
... 

Зверніть увагу: все це базується на припущенні, що SSN ніколи не змінюється для окремої людини (навіть не знаю, де насправді підтвердити цей факт з авторитетного джерела).

А у нас є Боб:

>>> bob = Person('bob', '1111-222-333', None)

Боб іде до судді, щоб він змінив своє ім'я:

>>> jim = Person('jim bo', '1111-222-333', 'sf bay area')

Це те, що ми знаємо:

>>> bob == jim
True

Але це два різні об’єкти з різною виділеною пам’яттю, як і два різні записи однієї людини:

>>> bob is jim
False

Тепер іде частина, де hash () зручний:

>>> dmv_appointments = {}
>>> dmv_appointments[bob] = 'tomorrow'

Вгадай що:

>>> dmv_appointments[jim] #?
'tomorrow'

З двох різних записів ви можете отримати доступ до однієї і тієї ж інформації. Тепер спробуйте це:

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

Що щойно сталося? Це зіткнення. Оскільки hash(jim) == hash(hash(jim))обидва цілі числа до речі, нам потрібно порівняти вхідні дані __getitem__з усіма елементами, що стикаються. Вбудований intне має ssnатрибута, тому він спрацьовує .

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

У цьому останньому прикладі я показую, що навіть при зіткненні виконується порівняння, об'єкти перестають бути рівними, а це означає, що воно успішно піднімає a KeyError.


Дійсно зручне пояснення. Як новачкові, це допомогло мені зрозуміти, як створити клас, який можна помістити в набори, і використовувати їх як ключі для словника / хеш-таблиці. Також якщо я збираю collection [hashable_obj] = hashable_obj, я можу отримати вказівник на цей екземпляр пізніше. Але скажіть мені, чи є кращий спосіб відстежувати такі колекції.
PaulDong

@dnozay Але, все-таки, вихід hash()- це ціле число фіксованого розміру, яке може спричинити зіткнення
надмірний обмін

2
Хтось може детальніше розказати про використання __eq__у наведеному вище прикладі. Чи викликається він словником, коли він намагається порівняти отриманий ключ із усіма наявними в ньому ключами? Такий що delв __eq__способі в останньому прикладі, словник не має ніякого відношення до виклику , щоб використовувати для визначення еквівалентності ключа він отримав з ключами у нього є?
Jet Blue

1
@JetBlue Пояснення "співачки" неповне в прикладі з ключем hash(jim). Person.__eq__викликається, оскільки існуючий ключ має той самий хеш, що і hash(jim)для забезпечення того, що використовується цей правильний ключ Person.__eq__. Помиляється, оскільки припускає, що other, тобто int, має ssnатрибут. Якби hash(jim)ключ не існував у словнику, його __eq__б не викликали. Це пояснює, коли пошук ключів може бути O (n): коли всі елементи мають однаковий хеш, __eq__необхідно використовувати всі, наприклад, у випадку, коли ключ не існує.
WloHu

1
Хоча я розумію педагогічний інтерес вашого прикладу, чи не було б простіше просто писати dmv_appointments[bob.ssn] = 'tomorrow', усуваючи необхідність визначати __hash__метод? Я розумію, що додає 4 символи для кожної зустрічі, яку ви пишете та читаєте, але мені це здається зрозумілішим.
Алексіс

3

Документи Python дляhash() стану:

Значення хешу - це цілі числа. Вони використовуються для швидкого порівняння ключів словника під час пошуку словника.

Словники Python реалізовані як хеш-таблиці. Тому будь-який раз, коли ви користуєтеся словником, hash()викликається клавіша, яку ви передаєте для призначення або пошуку.

Крім того, документи для стану dictтипу :

Значення, які не можна хешувати , тобто значення, що містять списки, словники або інші змінні типи (які порівнюються за значенням, а не за ідентичністю об'єкта), не можуть використовуватися як ключі.


1

Хеш використовується словниками та наборами для швидкого пошуку об’єкта. Гарною відправною точкою є стаття Вікіпедії про хеш-таблиці .


-2

Ви можете використовувати Dictionaryтип даних у python. Він дуже схожий на хеш - і він також підтримує вкладання, подібний до вкладеного хешу.

Приклад:

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'])

Для отримання додаткової інформації зверніться до цього підручника щодо типу даних словника .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.