web-dev-qa-db-fra.com

Fonction intégrée 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

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

80
sm1

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.

55
SilentGhost

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 .

88
Mike Hordecki

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.

32
rewritten

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 objets str, bytes et datetime 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 objets str, bytes et datetime.

Si PYTHONHASHSEED est défini sur une valeur entière, il est utilisé comme une graine fixe pour générer la hash() 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 valeur 0 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
16
arekolek

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
8
Tzury Bar Yochay

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.

6
George V. Reilly

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
6
Andrin von Rechenberg

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
5
Lion

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
3
osa

La valeur de PYTHONHASHSEED peut être utilisée pour initialiser les valeurs de hachage.

Essayer:

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