Волосся, незмінний


81

З недавнього запитання SO (див. Створення словника на python, який індексується списками ), я зрозумів, що, мабуть, я неправильно уявляв значення хеш-і незмінних об'єктів у python.

  • Що означає hashable на практиці?
  • Яке співвідношення між хешем і незмінним?
  • Чи є змінні об'єкти, що мають хеш, або незмінні об'єкти, які не хешуються?

Відповіді:


85

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

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

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

Щоб по-справжньому зрозуміти цю ідею, спробуйте реалізувати власну хеш-таблицю такою мовою, як C / C ++, або прочитати реалізацію Java- HashMapкласу.


1
Більше того, хеш-таблиці не можуть виявити, коли хеш їх ключів змінюється (принаймні будь-яким ефективним способом). Це звичайна помилка, наприклад, на Java, коли a HashMapстає зламаним, якщо ви модифікуєте об’єкт, що використовується в ньому як ключ: не можна знайти ні старий, ні новий ключ, хоча якщо ви надрукуєте карту, його можна побачити там.
дубль

1
Hashable та Immutable дещо пов’язані, але не однакові. Наприклад, екземпляри, створені з успадковування нестандартних класів, objectможна хешувати, але не змінювати. У цих примірниках можна використовувати ключі dict, але вони все одно можуть бути змінені, якщо їх передавати.
Pranjal Mittal

13
  • Чи є змінні об'єкти, що мають хеш, або незмінні об'єкти, які не хешуються?

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

>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
TypeError: unhashable type: 'list'

Типи, що розмиваються

  • Усі атомарні незмінні типи мають хеш, наприклад, str, байти, числові типи
  • Заморожений набір завжди хеш-хеш (його елементи повинні бути хеш-хешем за визначенням)
  • Кортеж можна хешувати, лише якщо всі його елементи хешуються
  • Типи, визначені користувачем, хешуються за замовчуванням, оскільки їх хеш-значенням є їх ідентифікатор ()

8

З глосарію Python :

Об'єкт можна хешувати, якщо він має хеш-значення, яке ніколи не змінюється протягом усього життя (йому потрібен __hash__()метод), і його можна порівняти з іншими об'єктами (йому потрібен метод __eq__()або __cmp__()). Хеш-об'єкти, які порівнюють рівні, повинні мати однакове хеш-значення.

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

Усі незмінні вбудовані об'єкти Python можна хешувати, тоді як жодні змінні контейнери (наприклад, списки чи словники) не мають. Об'єкти, що є екземплярами визначених користувачем класів, за замовчуванням хешуються; всі вони порівнюють нерівні, і їх хеш-значення - це їх id ().

Дікти та набори повинні використовувати хеш для ефективного пошуку в хеш-таблиці; значення хешу повинні бути незмінними, оскільки зміна хешу призведе до псування структур даних і призведе до помилки dict або set. Найпростіший спосіб зробити хеш-значення незмінним - зробити весь об’єкт незмінним, саме тому ці два часто згадуються разом.

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


@Andrey: frozensets можна розширювати, набори - ні; обидва можуть містити лише елементи, що розширюються. У тих місцях, про які Марк згадував набори, він мав рацію, тому я не думаю, що він мав на увазі фрозенсетів.
tzot

12
Класи, визначені користувачем, за замовчуванням визначають хеш-типи (хеш - це лише об’єкт id). Це не може змінитися протягом життя об’єкта, тому його можна хеш, але це не означає, що ви не можете визначити змінні типи! Вибачте, але розширюваність не означає незмінність.
Скотт Гріффітс,

1
@ScottGriffiths Я не знаю, чому мені знадобилося 6 років, щоб побачити ваш коментар, але краще пізно, ніж ніколи. Я не знаю, як я міг бути настільки далеко, враховуючи те, що я нарікав на неможливість розміщення змінних об'єктів у наборі C ++. Сподіваюся, мої редагування виправлять речі.
Марк Ренсом

7

Технічно hashable означає, що клас визначає __hash__(). Відповідно до документів:

__hash__()має повернути ціле число. Єдиною обов’язковою властивістю є те, що об’єкти, які порівнюють рівні, мають однакове хеш-значення; рекомендується якось змішувати (наприклад, використовуючи ексклюзивні або) хеш-значення для компонентів об’єкта, які також відіграють певну роль у порівнянні об’єктів.

Я думаю, що для вбудованих типів Python всі хеш-типи також незмінні.

Було б важко, але, можливо, неможливо мати змінний об’єкт, який тим не менше визначався __hash__().


1
Варто зазначити, що __hash__за замовчуванням визначається повернення об’єкта id; вам потрібно вийти з усіх сил, __hash__ = Noneщоб зробити це незмінним. Крім того, як Марк Ренсом зазначає, є додаткова умова, що її можна хешувати лише в тому випадку, якщо хеш-значення ніколи не може змінитися!
Скотт Гріффітс,

5
Я думаю, що відповідь трохи вводить в оману, listвизначає __hash__у тому сенсі, що hasattr([1,2,3], "__hash__")повертається True, однак виклик hash([1,2,3])викликає a TypeError(Python 3), тому він не зовсім хеш. Послання на існування __hash__недостатньо для того, щоб визначити, чи є щось а) хешувальним б) незмінним
Матті Ліра

4

Існує неявний, навіть якщо відсутній явний зв’язок, що вимушений між незмінним та хешувальним, завдяки взаємодії між ними

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

Тут немає проблем, якщо ви не перевизначите, __eq__щоб клас об’єктів визначав еквівалентність за значенням.

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

Важко побачити програму, де це можливо, розглянемо можливий клас А, який відповідає цим вимогам. Хоча є очевидний вироджений випадок, коли __hash__повертає константу.

Зараз: -

>>> a = A(1)
>>> b = A(1)
>>> c = A(2)
>>> a == b
True
>>> a == c
False
>>> hash(a) == hash(b)
True
>>> a.set_value(c)
>>> a == c
True
>>> assert(hash(a) == hash(c)) # Because a == c => hash(a) == hash(c)
>>> assert(hash(a) == hash(b)) # Because hash(a) and hash(b) have compared equal 
                                 before and the result must stay static over the objects lifetime.

Насправді це означає при створенні хеш (b) == хеш (c), незважаючи на те, що ніколи не порівнюються рівні. Я намагаюся все-таки побачити, як корисно визначити __hash__() для змінного об'єкта, який визначає порівняння за значенням.

Примітка : __lt__, __le__, __gt__і __ge__comparsions не впливає , так що ви все ще можете визначити порядок hashable об'єктів, що змінюються чи іншим чином в залежності від їх вартості.


3

саме тому, що це найпопулярніший хіт Google, ось простий спосіб зробити змінний об’єкт хешируемым:

>>> class HashableList(list):
...  instancenumber = 0  # class variable
...  def __init__(self, initial = []):
...   super(HashableList, self).__init__(initial)
...   self.hashvalue = HashableList.instancenumber
...   HashableList.instancenumber += 1
...  def __hash__(self):
...   return self.hashvalue
... 
>>> l = [1,2,3]
>>> m = HashableList(l)
>>> n = HashableList([1,2,3])
>>> m == n
True
>>> a={m:1, n:2}
>>> a[l] = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> m.hashvalue, n.hashvalue
(0, 1)

Я насправді знайшов щось подібне, коли створював клас для перекидання записів SQLAlchemy у щось мінливе і корисніше для мене, зберігаючи при цьому їхню зручність для використання як клавіші dict.


3

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

Хешабельність дещо інша і стосується порівняння.

hashable Об'єкт є hashable, якщо він має хеш-значення, яке ніколи не змінюється протягом свого життя (йому потрібен__hash__()метод), і його можна порівняти з іншими об'єктами (йому потрібен__eq__()або__cmp__()метод). Хеш-об'єкти, які порівнюють рівні, повинні мати однакове хеш-значення.

Усі визначені користувачем класи мають __hash__метод, який за замовчуванням просто повертає ідентифікатор об'єкта. Отже, об’єкт, який відповідає критеріям хешируемості, не обов’язково є незмінним.

Об'єкти будь-якого нового класу, який ви оголошуєте, можуть використовуватися як ключ словника, якщо ви не запобігаєте цьому, наприклад, кидаючи з __hash__

Можна сказати, що всі незмінні об'єкти можна хешувати, тому що якщо хеш змінюється протягом життя об'єкта, то це означає, що об'єкт мутував.

Але не зовсім. Розглянемо кортеж, який має список (змінний). Деякі кажуть, що кортеж незмінний, але в той же час він дещо не розмивається (кидає).

d = dict()
d[ (0,0) ] = 1    #perfectly fine
d[ (0,[0]) ] = 1  #throws

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


1
"Хеш-об'єкти, які порівнюють рівні, повинні мати однакове хеш-значення." Чому? Я можу створювати об'єкти, які порівнюють рівні, але не мають однакового хеш-значення.
ендоліт

1
Можна створити такі об'єкти, але це було б порушенням концепції, визначеної в документації Python. Ідея полягає в тому, що насправді ми можемо використати цю вимогу для отримання такого (логічно еквівалентного) підтексту: Якщо хеші не рівні, то об’єкти не рівні. Дуже корисний. Багато реалізацій, контейнери та алгоритми покладаються на це значення, щоб пришвидшити процес.
user2622016

Поширені випадки, коли comparison != identityвідбувається порівняння "недійсних" значень разом, наприклад float("nan") == float("nan"), або інтернованих рядків із фрагментів: "apple" is "apple"проти"apple" is "crabapple"[4:]
sleblanc

1

У Python вони переважно взаємозамінні; оскільки хеш повинен представляти вміст, тож він настільки ж мінливий, як і об’єкт, а зміна об’єкта хеш-значення зробить його непридатним для використання як ключ dict.

В інших мовах хеш-значення більше пов'язане з "ідентичністю" об'єктів, а не (обов'язково) зі значенням. Таким чином, для змінного об’єкта вказівник може бути використаний для запуску хешування. Припускаючи, звичайно, що об’єкт не рухається в пам’яті (як це роблять деякі GC). Наприклад, такий підхід застосовується у Lua. Це робить змінний об’єкт придатним для використання в якості ключа таблиці; але створює кілька (неприємних) сюрпризів для новачків.

Врешті-решт, наявність незмінного типу послідовності (кортежі) робить його приємнішим для «багатозначних ключів».


3
@javier: "У Python вони в основному взаємозамінні". Мої сумніви стосуються малої частини, не включеної в "в основному"
Хоакін

0

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

Сподіваюся, це допомагає ...

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