Встроенная функция Python hash ()

83

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

Ответы:

57

Используйте hashlib, поскольку он hash() был разработан для :

быстро сравнить ключи словаря во время поиска в словаре

и поэтому не гарантирует, что он будет одинаковым во всех реализациях Python.

Тихий призрак
источник
5
Разве хэш-функции не работают hashlibнемного медленно для некриптографического использования?
Brandon Rhodes
8
На самом деле они очень медленные по сравнению с хэш-функциями общего назначения, такими как Jenkins, Bernstein, FNV, MurmurHash и многими другими. Если вы хотите создать свою собственную структуру, похожую на хэш-таблицу, я предлагаю посмотреть uthash.h uthash.sourceforge.net
lericson
46
Ориентиры: hash95 ns, binascii.crc32570 ns, hashlib.md5.digest()1.42 us, murmur.string_hash234 ns
temoto
hashиспользует новое случайно сгенерированное значение соли с каждым сеансом Python. Таким образом, он будет меняться между сеансами Python.
hobs
89

Как указано в документации, встроенная функция hash () не предназначена для хранения результирующих хэшей где-то извне. Он используется для предоставления хеш-значения объекта, для хранения его в словарях и так далее. Это также зависит от реализации (GAE использует модифицированную версию Python). Проверять, выписываться:

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

Как видите, они разные, поскольку hash () использует __hash__метод объекта вместо «обычных» алгоритмов хеширования, таких как SHA.

Учитывая вышеизложенное, рациональным выбором является использование модуля hashlib .

Майк Хордеки
источник
Спасибо! Я пришел сюда, задаваясь вопросом, почему я всегда получаю разные хеш-значения для одинаковых объектов, что приводит к неожиданному поведению с dicts (которые индексируются по типу hash +, а не проверяют равенство). Быстрый способ сгенерировать собственный хэш int из hashlib.md5 int(hashlib.md5(repr(self)).hexdigest(), 16)(при условии, что self.__repr__он был определен как идентичный, если объекты идентичны). Если 32 байта слишком длинные, вы, конечно, можете уменьшить размер, разрезав шестнадцатеричную строку перед преобразованием.
Alan Plum
1
Во-вторых, если __repr__он достаточно уникален, вы можете просто использовать str.__hash__(т.е. hash(repr(self))), поскольку dicts не смешивают неравные объекты с одним и тем же хешем. Это работает, только если объект достаточно тривиален, чтобы repr мог представлять личность, очевидно.
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.

Это предназначено для обеспечения защиты от отказа в обслуживании, вызванного тщательно подобранными входами, которые используют наихудшую производительность вставки dict, сложность 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.
Alexander Huszagh
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

А как насчет бит знака?

Например:

Значение Hex 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 или напишите свою собственную хеш-функцию.

ewanm89
источник