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

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


person Community    schedule 27.04.2009    source источник
comment
это связано с тем, что ваш winxp - 32-битная платформа, а Google - 64-битная   -  person Tzury Bar Yochay    schedule 29.03.2011


Ответы (11)


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

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

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

person SilentGhost    schedule 27.04.2009
comment
Разве хеш-функции в hashlib не медленны для некриптографического использования? - person Brandon Rhodes; 14.11.2010
comment
На самом деле они очень медленные по сравнению с хэш-функциями общего назначения, такими как Jenkins, Bernstein, FNV, MurmurHash и многими другими. Если вы хотите создать свою собственную структуру, похожую на хеш-таблицу, я предлагаю посмотреть на uthash.h uthash.sourceforge.net - person lericson; 04.02.2011
comment
Тесты: hash 95 нс, binascii.crc32 570 нс, hashlib.md5.digest() 1.42 мкс, murmur.string_hash 234 нс - person temoto; 14.03.2012
comment
hash использует новое случайно сгенерированное значение соли с каждым сеансом Python. Таким образом, он будет меняться между сеансами Python. - person hobs; 24.08.2020
comment
Даже не гарантируется, что процесс Python будет одинаковым для всех процессов, запущенных на одном компьютере! Попробуйте запустить echo "print(hash('hej'))" | python3 - несколько раз и каждый раз обратите внимание на разные результаты (python 3.6). - person Moberg; 10.03.2021

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

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

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

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

person Mike Hordecki    schedule 27.04.2009
comment
Спасибо! Я пришел сюда, задаваясь вопросом, почему я всегда получаю разные хеш-значения для идентичных объектов, что приводит к неожиданному поведению с dicts (которые индексируются по типу hash +, а не проверяют равенство). Быстрый способ сгенерировать собственный хэш int из hashlib.md5 - int(hashlib.md5(repr(self)).hexdigest(), 16) (при условии, что self.__repr__ был определен как идентичный, если объекты идентичны). Если 32 байта слишком длинные, вы, конечно, можете уменьшить размер, нарезав шестнадцатеричную строку перед преобразованием. - person Alan Plum; 28.03.2010
comment
Если подумать, если __repr__ достаточно уникален, вы можете просто использовать str.__hash__ (т.е. hash(repr(self))), поскольку dicts не смешивают неравные объекты с одним и тем же хешем. Это работает, только если объект достаточно тривиален, чтобы repr мог представлять идентичность, очевидно. - person Alan Plum; 28.03.2010
comment
Итак, в вашем примере с двумя объектами a и b, как я могу использовать модуль hashlib, чтобы убедиться, что объекты идентичны? - person Garrett; 05.08.2014

Ответ совершенно не удивителен: на самом деле

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.

person rewritten    schedule 20.10.2010

Большинство ответов предполагают, что это связано с разными платформами, но это еще не все. Из документации 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
person arekolek    schedule 17.11.2015
comment
Это верно только для Python 3.x, но поскольку Python 3 - это настоящее и будущее, и это единственный ответ, который решает эту проблему, +1. - person Alexander Huszagh; 20.11.2015

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

Если рассчитанный хэш должен быть одинаковым на обеих платформах, рассмотрите возможность использования

def hash32(value):
    return hash(value) & 0xffffffff
person Tzury Bar Yochay    schedule 29.03.2011

Предположительно, AppEngine использует 64-битную реализацию Python (-5768830964305142685 не влезет в 32-битную версию), а ваша реализация Python - 32-битная. Вы не можете полагаться на то, что хэши объектов будут значимо сопоставимы между различными реализациями.

person George V. Reilly    schedule 26.05.2010

Это хеш-функция, которую 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
person Andrin von Rechenberg    schedule 20.02.2012
comment
Можете ли вы поделиться каким-либо контекстом о том, для чего используется эта хеш-функция и почему? - person amcnabb; 08.11.2012

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

Например:

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

Будьте осторожны при использовании:

def hash32(value):
    return hash(value) & 0xffffffff
person Lion    schedule 13.01.2012

Полиномиальный хеш для строк. 1000000009 и 239 - произвольные простые числа. Случайное столкновение маловероятно. Модульная арифметика не очень быстрая, но для предотвращения коллизий это более надежно, чем взятие ее по модулю степени 2. Конечно, нарочно найти столкновение легко.

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

Значение PYTHONHASHSEED может использоваться для инициализации хеш-значений.

Пытаться:

PYTHONHASHSEED python -c 'print(hash('http://stackoverflow.com'))'
person blueyed    schedule 19.10.2015

Вероятно, он просто запрашивает функцию, предоставляемую операционной системой, а не свой собственный алгоритм.

Как говорится в других комментариях, используйте hashlib или напишите свой собственная хеш-функция.

person ewanm89    schedule 27.04.2009