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
.