Windows XP, Python 2.5:
hash('http://stackoverflow.com') Result: 1934711907
Google App Engine ( http://Shell.appspot.com/ ):
hash('http://stackoverflow.com') Result: -5768830964305142685
Pourquoi donc? Comment puis-je avoir une fonction de hachage qui me donnera les mêmes résultats sur différentes plates-formes (Windows, Linux, Mac)?
Utilisez hashlib comme hash()
a été conçu pour être utilisé :
comparer rapidement les clés du dictionnaire lors d'une recherche dans le dictionnaire
et ne garantit donc pas qu'il en ira de même pour les implémentations de Python.
Comme indiqué dans la documentation, la fonction hash () intégrée est pas conçue pour stocker les hachages résultants quelque part à l'extérieur. Il est utilisé pour fournir la valeur de hachage de l'objet, pour le stocker dans des dictionnaires, etc. Il est également spécifique à l'implémentation (GAE utilise une version modifiée de Python). Check-out:
>>> class Foo:
... pass
...
>>> a = Foo()
>>> b = Foo()
>>> hash(a), hash(b)
(-1210747828, -1210747892)
Comme vous pouvez le constater, ils sont différents, puisque hash () utilise l'objet __hash__
méthode à la place des algorithmes de hachage "normaux", tels que SHA.
Compte tenu de ce qui précède, le choix rationnel consiste à utiliser le module hashlib .
La réponse n’est absolument pas une surprise: en fait
In [1]: -5768830964305142685L & 0xffffffff
Out[1]: 1934711907L
donc si vous voulez obtenir des réponses fiables sur ASCII chaînes, obtenez simplement les 32 bits inférieurs en tant que uint
. La fonction de hachage pour les chaînes est 32- bit-safe et presque portable.
D'un autre côté, vous ne pouvez absolument pas compter sur _ pour obtenir la hash()
d'un objet sur lequel vous n'avez pas explicitement défini la méthode __hash__
Comme invariante.
Over ASCII chaînes cela fonctionne uniquement parce que le hachage est calculé sur les seuls caractères constituant la chaîne, comme suit:
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
où la fonction c_mul
est la multiplication "cyclique" (sans dépassement de capacité) comme en C.
La plupart des réponses suggèrent que cela est dû à différentes plates-formes, mais il y a plus que cela. De la documentation de object.__hash__(self)
:
Par défaut, les valeurs
__hash__()
des objetsstr
,bytes
etdatetime
sont "salées" avec une valeur aléatoire imprévisible. Bien qu'ils restent constants au sein d'un processus individuel Python, ils ne sont pas prévisibles entre des invocations répétées de Python.Ceci est destiné à fournir une protection contre un déni de service causé par des entrées soigneusement choisies qui exploitent les pires performances d'une insertion dict, O (n²) de complexité. Voir http://www.ocert.org/advisories/ocert-2011-003.html pour plus de détails.
La modification des valeurs de hachage affecte l'ordre d'itération de
dicts
,sets
et d'autres mappages. Python n'a jamais donné de garantie sur cet ordre (et il varie généralement entre les versions 32 bits et 64 bits).
Même si vous utilisez la même machine, vous obtiendrez des résultats variables selon les invocations:
$ python -c "print(hash('http://stackoverflow.com'))"
-3455286212422042986
$ python -c "print(hash('http://stackoverflow.com'))"
-6940441840934557333
Tandis que:
$ python -c "print(hash((1,2,3)))"
2528502973977326415
$ python -c "print(hash((1,2,3)))"
2528502973977326415
Voir aussi la variable d'environnement PYTHONHASHSEED
:
Si cette variable n'est pas définie ou définie sur
random
, une valeur aléatoire est utilisée pour ensemencer les hachages des objetsstr
,bytes
etdatetime
.Si
PYTHONHASHSEED
est défini sur une valeur entière, il est utilisé comme une graine fixe pour générer lahash()
des types couverts par la randomisation de hachage.Son but est de permettre un hachage reproductible, tel que des auto-tests pour l'interprète lui-même, ou de permettre à un cluster de processus python de partager des valeurs de hachage.
Le nombre entier doit être un nombre décimal compris dans l'intervalle
[0, 4294967295]
. Spécifier la valeur0
Désactivera la randomisation par hachage.
Par exemple:
$ export PYTHONHASHSEED=0
$ python -c "print(hash('http://stackoverflow.com'))"
-5843046192888932305
$ python -c "print(hash('http://stackoverflow.com'))"
-5843046192888932305
Les résultats de hachage varient entre les plates-formes 32 bits et 64 bits
Si le hachage calculé doit être identique sur les deux plates-formes, envisagez d'utiliser
def hash32(value):
return hash(value) & 0xffffffff
AppEngine utilise une implémentation 64 bits de Python (-5768830964305142685 ne rentre pas dans 32 bits)) et votre implémentation de Python est 32 Vous ne pouvez pas compter sur le fait que les hachages d’objet sont comparables entre différentes implémentations.
C’est la fonction de hachage que Google utilise en production pour 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
Qu'en est-il du bit de signe?
Par exemple:
Valeur hexadécimale 0xADFE74A5
représente non signé 2919134373
et signé -1375832923
. La valeur actuelle doit être signée (bit de signature = 1) mais python la convertit en signe non signé et nous avons une valeur de hachage incorrecte après traduction de 64 à 32 bits.
Soyez prudent en utilisant:
def hash32(value):
return hash(value) & 0xffffffff
Polynôme de hachage pour les chaînes. 1000000009
et 239
sont des nombres premiers arbitraires. Peu probable d'avoir des collisions par accident. L’arithmétique modulaire n’est pas très rapide, mais elle est plus fiable pour éviter les collisions que de lui donner modulo une puissance de 2
. Bien sûr, il est facile de trouver une collision à dessein.
mod=1000000009
def hash(s):
result=0
for c in s:
result = (result * 239 + ord(c)) % mod
return result % mod
La valeur de PYTHONHASHSEED peut être utilisée pour initialiser les valeurs de hachage.
Essayer:
PYTHONHASHSEED python -c 'print(hash('http://stackoverflow.com'))'