Вбудована функція hash () Python


82

Windows XP, Python 2.5:

hash('http://stackoverflow.com') Result: 1934711907

Google App Engine ( http://shell.appspot.com/ ):

hash('http://stackoverflow.com') Result: -5768830964305142685

Чому так? Як я можу мати хеш-функцію, яка дасть мені однакові результати на різних платформах (Windows, Linux, Mac)?


14
це завдячує тому, що ваш winxp є 32-бітною платформою, а google - 64-бітною
Tzury Bar Yochay

Відповіді:


56

Використовуйте hashlib, як hash() було розроблено для використання для :

швидко порівняти ключі словника під час пошуку словника

і тому не гарантує, що вона буде однаковою у реалізаціях Python.


5
Чи не є хеш-функції hashlibтрохи повільними для некриптографічного використання?
Brandon Rhodes

8
Вони насправді дуже повільні в порівнянні із загальними хеш-функціями, такими як Дженкінс, Бернштейн, FNV, MurmurHash та багато інших. Якщо ви хочете створити власну структуру, подібну до хеш-таблиці, я пропоную подивитися uthash.h uthash.sourceforge.net
lericson

45
Тести: hash95 нс, binascii.crc32570 нс, hashlib.md5.digest()1,42 murmur.string_hash
сс

hashвикористовує нове випадково сформоване значення солі з кожним сеансом python. Тож це буде змінюватися між сесіями python.
конфорки

89

Як зазначено в документації, вбудована функція hash () не призначена для зберігання отриманих хешів десь зовні. Він використовується для надання хеш-значення об’єкта, для їх зберігання у словниках тощо. Це також залежить від реалізації (GAE використовує модифіковану версію Python). Перевіряти:

>>> class Foo:
...     pass
... 
>>> a = Foo()
>>> b = Foo()
>>> hash(a), hash(b)
(-1210747828, -1210747892)

Як бачите, вони різні, оскільки hash () використовує __hash__метод об'єкта замість "звичайних" алгоритмів хешування, таких як SHA.

Враховуючи вищесказане, раціональним вибором є використання модуля hashlib .


Дякую! Я прийшов сюди, задаючись питанням, чому я завжди отримую різні хеш-значення для однакових об'єктів, що призводить до несподіваної поведінки з диктами (який індексується за хеш + типом, а не перевіряє на рівність). Швидким способом створити власний хеш int з hashlib.md5 є int(hashlib.md5(repr(self)).hexdigest(), 16)(припускаючи, що self.__repr__визначено як ідентичні, якщо об’єкти ідентичні). Якщо 32 байти занадто довгі, ви можете, звичайно, зменшити розмір, нарізавши шістнадцятковий рядок перед перетворенням.
Alan Plum

1
Якщо добре подумати, якщо __repr__він досить унікальний, ви можете просто використовувати його str.__hash__(тобто hash(repr(self))), оскільки дикти не змішують нерівні об’єкти з однаковим хешем. Це працює лише в тому випадку, якщо об'єкт досить тривіальний, щоб вираз, очевидно, міг представляти особу.
Alan Plum

Отже, у вашому прикладі з двома об’єктами aі b, як я міг використовувати модуль hashlib, щоб побачити, що об’єкти ідентичні?
Гарретт


32

Відповідь абсолютно не дивно: насправді

In [1]: -5768830964305142685L & 0xffffffff
Out[1]: 1934711907L

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

З іншого боку, ви взагалі не можете покладатися на отримання hash()будь-якого об’єкта, над яким ви не визначили явний __hash__метод як інваріантний.

Над рядками ASCII це працює лише тому, що хеш обчислюється на окремі символи, що утворюють рядок, як показано нижче:

class string:
    def __hash__(self):
        if not self:
            return 0 # empty
        value = ord(self[0]) << 7
        for char in self:
            value = c_mul(1000003, value) ^ ord(char)
        value = value ^ len(self)
        if value == -1:
            value = -2
        return value

де c_mulфункцією є "циклічне" множення (без переповнення), як у C.


18

Більшість відповідей припускають, що це пов’язано з різними платформами, але в цьому є й більше. З документаціїobject.__hash__(self) :

За замовчуванням __hash__()значення str, bytesі datetimeоб'єкти «солоні» з непередбачуваним випадковим значенням. Хоча вони залишаються незмінними в межах окремого процесу Python, їх не можна передбачити між повторними викликами Python.

Це призначено для забезпечення захисту від відмови в обслуговуванні, спричиненої ретельно підібраними вхідними даними, які використовують найгіршу ефективність вставки дикту, складність O (n²). Детальніше див. На http://www.ocert.org/advisories/ocert-2011-003.html .

Зміна значення хеш - функції впливає на порядок ітерації dicts, sets і інших відображень. Python ніколи не давав гарантій щодо цього впорядкування (і воно, як правило, коливається між 32-розрядною та 64-розрядною збірками).

Навіть запуск на одній машині дасть різні результати для всіх викликів:

$ python -c "print(hash('http://stackoverflow.com'))"
-3455286212422042986
$ python -c "print(hash('http://stackoverflow.com'))"
-6940441840934557333

Поки:

$ python -c "print(hash((1,2,3)))"
2528502973977326415
$ python -c "print(hash((1,2,3)))"
2528502973977326415

Дивіться також змінну середовища PYTHONHASHSEED:

Якщо ця змінна не встановлена або встановлена на random, випадкове значення використовується для насіння хеші str, bytesі datetimeоб'єкти.

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

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

Ціле число має бути десятковим числом у діапазоні [0, 4294967295]. Вказівка ​​значення 0вимкне рандомізацію хешу.

Наприклад:

$ export PYTHONHASHSEED=0                            
$ python -c "print(hash('http://stackoverflow.com'))"
-5843046192888932305
$ python -c "print(hash('http://stackoverflow.com'))"
-5843046192888932305

3
Це справедливо лише для Python 3.x, але оскільки Python 3 є сьогоденням і майбутнім, і це єдина відповідь, яка стосується цього, +1.
Олександр Хузаг

8

Результати хешування варіюються між 32-бітовою та 64-бітною платформами

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

def hash32(value):
    return hash(value) & 0xffffffff

6

На думку, AppEngine використовує 64-розрядну реалізацію Python (-5768830964305142685 не вміщується в 32 біти), а ваша реалізація Python - 32 біти. Ви не можете покластися на те, що хеші об'єктів є істотно порівнянними між різними реалізаціями.


6

Це хеш-функція, яку Google використовує у виробництві для python 2.5:

def c_mul(a, b):
  return eval(hex((long(a) * b) & (2**64 - 1))[:-1])

def py25hash(self):
  if not self:
    return 0 # empty
  value = ord(self[0]) << 7
  for char in self:
    value = c_mul(1000003, value) ^ ord(char)
  value = value ^ len(self)
  if value == -1:
    value = -2
  if value >= 2**63:
    value -= 2**64
  return value

7
Чи можете ви поділитися будь-яким контекстом щодо того, для чого використовується ця хеш-функція і чому?
amcnabb

5

А як щодо знакового біта?

Наприклад:

Шістнадцяткове значення 0xADFE74A5представляє без підпису 2919134373та підпису-1375832923 . Правильне значення повинно бути підписане (біт знаку = 1), але python перетворює його як без знака, і ми маємо неправильне значення хешу після перекладу з 64 на 32 біт.

Будьте обережні, використовуючи:

def hash32(value):
    return hash(value) & 0xffffffff

3

Поліноміальний хеш для рядків. 1000000009і 239є довільними простими числами. Навряд чи випадково зіткнеться. Модульна арифметика не дуже швидка, але для запобігання зіткнення це більш надійно, ніж прийняття її за модулем потужності 2. Звичайно, зіткнення легко знайти навмисно.

mod=1000000009
def hash(s):
    result=0
    for c in s:
        result = (result * 239 + ord(c)) % mod
    return result % mod

2

Значення PYTHONHASHSEED може використовуватися для ініціалізації хеш-значень.

Спробуйте:

PYTHONHASHSEED python -c 'print(hash('http://stackoverflow.com'))'

-3

Ймовірно, він просто запитує функцію, надану операційною системою, а не власний алгоритм.

Як кажуть інші коментарі, використовуйте hashlib або напишіть власну хеш-функцію.

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