TL; DR:
Consulte el glosario : hash()
se utiliza como un atajo para comparar objetos, un objeto se considera hash si se puede comparar con otros objetos. por eso usamos hash()
. También se utiliza para el acceso dict
y set
elementos que se implementan como tablas hash de tamaño variable en CPython .
Consideraciones tecnicas
- Por lo general, comparar objetos (que pueden implicar varios niveles de recursividad) es caro.
- preferiblemente, la
hash()
función es un orden de magnitud (o varios) menos costosa.
- comparar dos hashes es más fácil que comparar dos objetos, aquí es donde está el atajo.
Si lee sobre cómo se implementan los diccionarios , estos usan tablas hash, lo que significa que derivar una clave de un objeto es una piedra angular para recuperar objetos en diccionarios en formato O(1)
. Sin embargo, eso depende mucho de su función hash para ser resistente a las colisiones . El peor caso para obtener un elemento en un diccionario es en realidad O(n)
.
En esa nota, los objetos mutables generalmente no son hash. La propiedad hash significa que puede usar un objeto como clave. Si el valor hash se utiliza como clave y el contenido de ese mismo objeto cambia, ¿qué debería devolver la función hash? ¿Es la misma clave o una diferente? Que depende de cómo se defina su función hash.
Aprendiendo con el ejemplo:
Imagina que tenemos esta clase:
>>> class Person(object):
... def __init__(self, name, ssn, address):
... self.name = name
... self.ssn = ssn
... self.address = address
... def __hash__(self):
... return hash(self.ssn)
... def __eq__(self, other):
... return self.ssn == other.ssn
...
Tenga en cuenta: todo esto se basa en la suposición de que el SSN nunca cambia para un individuo (ni siquiera sé dónde verificar ese hecho de una fuente autorizada).
Y tenemos a Bob:
>>> bob = Person('bob', '1111-222-333', None)
Bob va a ver a un juez para cambiar su nombre:
>>> jim = Person('jim bo', '1111-222-333', 'sf bay area')
Esto es lo que sabemos:
>>> bob == jim
True
Pero estos son dos objetos diferentes con diferente memoria asignada, al igual que dos registros diferentes de la misma persona:
>>> bob is jim
False
Ahora viene la parte donde hash () es útil:
>>> dmv_appointments = {}
>>> dmv_appointments[bob] = 'tomorrow'
Adivina qué:
>>> dmv_appointments[jim]
'tomorrow'
Desde dos registros diferentes puede acceder a la misma información. Ahora prueba esto:
>>> dmv_appointments[hash(jim)]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 9, in __eq__
AttributeError: 'int' object has no attribute 'ssn'
>>> hash(jim) == hash(hash(jim))
True
¿Lo que acaba de suceder? Eso es una colisión. Debido a hash(jim) == hash(hash(jim))
que ambos son enteros por cierto, necesitamos comparar la entrada de __getitem__
con todos los elementos que colisionan. El incorporado int
no tiene ningún ssn
atributo, por lo que se dispara.
>>> del Person.__eq__
>>> dmv_appointments[bob]
'tomorrow'
>>> dmv_appointments[jim]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: <__main__.Person object at 0x7f611bd37110>
En este último ejemplo, muestro que incluso con una colisión, se realiza la comparación, los objetos ya no son iguales, lo que significa que sube con éxito a KeyError
.