Ответ 1
Используйте hashlib, поскольку hash()
был предназначен для использования к:
быстро сравнивать словарные ключи во время поиска словаря
и, следовательно, не гарантирует, что он будет одинаковым для реализации Python.
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)?
Используйте hashlib, поскольку hash()
был предназначен для использования к:
быстро сравнивать словарные ключи во время поиска словаря
и, следовательно, не гарантирует, что он будет одинаковым для реализации Python.
Как указано в документации, встроенная функция hash() не предназначена для хранения полученных хэшей где-то снаружи. Он используется для предоставления хеш-значения объекта, для хранения их в словарях и т.д. Он также специфичен для реализации (GAE использует модифицированную версию Python). Выезд:
>>> class Foo:
... pass
...
>>> a = Foo()
>>> b = Foo()
>>> hash(a), hash(b)
(-1210747828, -1210747892)
Как вы можете видеть, они разные, поскольку hash() использует метод object __hash__
вместо "нормальных" алгоритмов хеширования, таких как SHA.
Учитывая вышеизложенное, рациональный выбор заключается в использовании модуля hashlib.
Ответ абсолютно не удивляет: на самом деле
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.
Большинство ответов подсказывают, что это из-за разных платформ, но для этого есть больше. Из документация 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 share hash values.
Целое число должно быть десятичным числом в диапазоне
[0, 4294967295]
. Указание значения0
отключит хеш-рандомизацию.
Например:
$ export PYTHONHASHSEED=0
$ python -c "print(hash('http://stackoverflow.com'))"
-5843046192888932305
$ python -c "print(hash('http://stackoverflow.com'))"
-5843046192888932305
Результаты хэши варьируются между 32-битными и 64-битными платформами.
Если вычисленный хэш должен быть одинаковым на обеих платформах, используйте
def hash32(value):
return hash(value) & 0xffffffff
В предположении, что AppEngine использует 64-битную реализацию Python (-5768830964305142685 не поместится в 32 бита), а ваша реализация Python - 32 бита. Вы не можете полагаться на хеши объектов, которые значимо сопоставимы между различными реализациями.
Это хэш-функция, которую 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
Как насчет знакового бита?
Например:
Hex value 0xADFE74A5
представляет unsigned 2919134373
и подписан -1375832923
.
Значение currect должно быть подписано (знак бит = 1), но python преобразует его как unsigned, и мы имеем неправильное значение хэша после перевода с 64 до 32 бит.
Будьте осторожны, используя:
def hash32(value):
return hash(value) & 0xffffffff
Полиномиальный хеш для строк. 1000000009
и 239
- произвольные простые числа. Вряд ли столкновения произойдут случайно. Модульная арифметика не очень быстро, но для предотвращения столкновений это более надежно, чем при ее модуле с мощностью 2
. Конечно, легко найти столкновение.
mod=1000000009
def hash(s):
result=0
for c in s:
result = (result * 239 + ord(c)) % mod
return result % mod
Значение PYTHONHASHSEED может использоваться для инициализации хэш-значений.
Try:
PYTHONHASHSEED python -c 'print(hash('http://stackoverflow.com'))'
Вероятно, он просто запрашивает операционную систему, а не собственный алгоритм.
Как говорится в других комментариях, используйте hashlib или напишите собственную хэш-функцию.