2009-04-27 8 views
80

Windows XP, Python 2.5:construido en función de pitón de hash()

hash('http://stackoverflow.com') Result: 1934711907 

Google App Engine (http://shell.appspot.com/):

hash('http://stackoverflow.com') Result: -5768830964305142685 

¿Por qué es eso? ¿Cómo puedo tener una función hash que me dará los mismos resultados en diferentes plataformas (Windows, Linux, Mac)?

+14

esto se debe al hecho de que su WinXP es una plataforma de 32 bits, mientras que Google es de 64 bits –

Respuesta

54

Uso hashlib como hash()was designed to be used to:

comparar rápidamente claves de diccionarios durante una búsqueda de diccionario

y por lo tanto no garantiza que será el mismo en las implementaciones de Python.

+5

¿No son las funciones de hash en 'hashlib' un poco lento para no criptográfica ¿utilizar? –

+0

@Brandon: ¿verdad? puntos de referencia? parches? – SilentGhost

+8

En realidad son muy lentos en comparación con las funciones hash de propósito general como Jenkins, Bernstein, FNV, MurmurHash y muchos otros. Si está buscando hacer su propia estructura tipo tabla hash, sugiero ver uthash.h http://uthash.sourceforge.net/ – lericson

-3

Probablemente solo le pregunte a la función proporcionada por el sistema operativo, en lugar de su propio algoritmo.

Como dicen otros comentarios, use hashlib o escriba su propia función hash.

88

Como se indica en la documentación, la función hash incorporada() es no diseñada para almacenar hashes resultantes en algún lugar externo. Se utiliza para proporcionar el valor de hash del objeto, para almacenarlos en diccionarios, etc. También es específico de la implementación (GAE usa una versión modificada de Python). Salida:

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

Como se puede ver, son diferentes, como de hash() utiliza el método en lugar de algoritmos hash 'normales', como SHA del objeto __hash__.

Dado lo anterior, la elección racional es utilizar el módulo hashlib.

+0

¡Gracias! Vine aquí preguntándome por qué siempre obtendría diferentes valores de hash para objetos idénticos, lo que resulta en un comportamiento inesperado con los dicts (que indexan por tipo de hash + en lugar de verificar la igualdad). Una forma rápida de generar su propio hash int de hashlib.md5 es 'int (hashlib.md5 (repr (self)). Hexdigest(), 16)' (suponiendo que 'self .__ repr__' ha sido definido como si fueran objetos iff idénticos) Son identicos). Si 32 bytes son demasiado largos, puede cortar el tamaño hacia abajo cortando la cadena hexadecimal antes de la conversión. –

+1

Pensándolo bien, si '__repr__' es lo suficientemente único, puedes usar' str .__ hash__' (es decir 'hash (repr (self))') ya que los dicts no mezclan objetos no iguales con el mismo hash. Esto solo funciona si el objeto es lo suficientemente trivial como para que la representación pueda representar una identidad, obviamente. –

+0

Entonces, en su ejemplo con dos objetos 'a' y' b', ¿cómo podría usar el módulo hashlib para ver que los objetos son idénticos? – Garrett

6

Supongo que AppEngine está utilizando una implementación de 64 bits de Python (-5768830964305142685 no cabe en 32 bits) y su implementación de Python es de 32 bits. No puede confiar en que los hash de objetos sean significativamente comparables entre diferentes implementaciones.

32

La respuesta es absolutamente ninguna sorpresa: de hecho

In [1]: -5768830964305142685L & 0xffffffff 
Out[1]: 1934711907L 

por lo que si desea obtener respuestas fiables sobre cadenas de caracteres ASCII, acaba de obtener los 32 bits inferiores como uint. La función hash para cadenas es 32-bit-safe y casi portable.

Por otro lado, no puede confiar en absoluto en que el hash() de cualquier objeto sobre el que no haya definido explícitamente el método __hash__ sea invariante.

lo largo de cadenas de caracteres ASCII que funciona sólo porque el hash se calcula sobre los personajes individuales que forman la cadena, como el siguiente:

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 

donde la función c_mul es la multiplicación "cíclico" (sin rebosadero) como en DO.

8

resultados Hash varía entre plataformas de 32 y 64 bits

Si un hash calculado será el mismo en ambas plataformas considere el uso de

def hash32(value): 
    return hash(value) & 0xffffffff 
5

¿Qué hay de bit de signo?

Por ejemplo:

valor hexadecimal sin signo representa 0xADFE74A52919134373 y firmaron -1375832923. El valor de corrección debe estar firmado (bit de signo = 1) pero python lo convierte como no firmado y tenemos un valor de hash incorrecto después de la conversión de 64 a 32 bits.

Tenga cuidado usando:

def hash32(value): 
    return hash(value) & 0xffffffff 
6

Ésta es la función hash que Google utiliza en la producción de 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

¿Puedes compartir cualquier contexto sobre para qué se utiliza esta función hash y por qué? – amcnabb

3

de hash polinómica para cuerdas. 1000000009 y 239 son números primos arbitrarios. Es poco probable que tenga colisiones por accidente. La aritmética modular no es muy rápida, pero para prevenir colisiones, esto es más confiable que tomar el módulo una potencia de 2. Por supuesto, es fácil encontrar una colisión a propósito.

mod=1000000009 
def hash(s): 
    result=0 
    for c in s: 
     result = (result * 239 + ord(c)) % mod 
    return result % mod 
1

El valor de PYTHONHASHSEED podría ser utilizado para inicializar los valores de hash.

Probar:

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

mayoría de las respuestas sugieren que esto es debido a las diferentes plataformas, pero hay más que eso. De the documentation of object.__hash__(self):

Por defecto, los valores de __hash__()str, bytes y datetime objetos se “salada” con un valor aleatorio impredecible. Aunque permanecen constantes dentro de un proceso de Python individual, no son predecibles entre invocaciones repetidas de Python.

Esto está diseñado para proporcionar protección contra una denegación de servicio causada por entradas cuidadosamente seleccionadas que explotan el peor de los casos rendimiento de una inserción dict, complejidad O (n²). Vea http://www.ocert.org/advisories/ocert-2011-003.html para más detalles.

El cambio de los valores de hash afecta al orden de iteración de dicts, sets y otras asignaciones. Python nunca ha hecho garantías sobre este pedido (y generalmente varía entre compilaciones de 32 bits y de 64 bits).

Incluso se ejecutan en la misma máquina producirá resultados variables a través de invocaciones:

$ python -c "print(hash('http://stackoverflow.com'))" 
-3455286212422042986 
$ python -c "print(hash('http://stackoverflow.com'))" 
-6940441840934557333 

bien:

$ python -c "print(hash((1,2,3)))" 
2528502973977326415 
$ python -c "print(hash((1,2,3)))" 
2528502973977326415 

Véase también la variable de entorno PYTHONHASHSEED:

Si esta variable no está configurada o configurada en random, se usa un valor aleatorio para generar los valores hash de los objetos str, bytes y datetime.

Si PYTHONHASHSEED se establece en un valor entero, se utiliza como un semilla fija para generar la hash() de los tipos cubiertos por el hash aleatorización.

Su propósito es permitir hashing repetible, tal como por autocomprobaciones para el intérprete sí mismo, o para permitir que un grupo de procesos de pitón para valores de las acciones de patata.

El número entero debe ser un número decimal en el rango [0, 4294967295]. Al especificar el valor 0 se deshabilitará la asignación aleatoria de hash.

Por ejemplo:

$ export PYTHONHASHSEED=0        
$ python -c "print(hash('http://stackoverflow.com'))" 
-5843046192888932305 
$ python -c "print(hash('http://stackoverflow.com'))" 
-5843046192888932305 
+3

Esto solo es cierto para Python 3.x, pero dado que Python 3 es el presente y el futuro y esta es la única respuesta que aborda esto, +1. –