El hash predeterminado para las clases definidas por el usuario es simplemente devolver su id. Esto proporciona un comportamiento que a menudo es útil; utilizar una instancia de una clase definida por el usuario como una clave de diccionario permitirá recuperar el valor asociado cuando se proporcione exactamente el mismo objeto para buscar el valor. por ejemplo:
>>> class Foo(object):
def __init__(self, foo):
self.foo = foo
>>> f = Foo(10)
>>> d = {f: 10}
>>> d[f]
10
Esto coincide con el de igualdad predeterminado de clases definidas por el usuario:
>>> g = Foo(10)
>>> f == g
False
>>> d[g]
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
d[g]
KeyError: <__main__.Foo object at 0x0000000002D69390>
Tenga en cuenta que a pesar de que f
y g
tienen los mismos valores para sus atributos, no son iguales y mirando hacia arriba g
en d
no encuentra el valor almacenado en f
. Además, incluso si cambiamos el valor de f.foo
, mirando hacia arriba f
en d
todavía encuentra el valor:
>>> f.foo = 11
>>> d[f]
10
La suposición es que los casos de alguna nueva clase arbitraria deben ser tratados como no equivalente, a menos que el programador específicamente declara las condiciones para que dos instancias se traten como equivalentes definiendo __eq__
y __hash__
.
Y esto funciona bastante; si defino una clase Car
, probablemente considero que dos autos con atributos idénticos representan dos automóviles diferentes. Si tengo un diccionario mapeando autos a propietarios registrados, no quiero encontrar a Alice cuando busco el auto de Bob, ¡incluso si Alice y Bob tienen automóviles idénticos! OTOH, si defino una clase para representar códigos postales, probablemente quiero considerar dos objetos diferentes con el mismo código para ser representaciones intercambiables de "lo mismo", y en este caso si tuviera un diccionario mapeando códigos postales a estados , Claramente querría poder encontrar el mismo estado con dos objetos diferentes que representan el mismo código postal.
Me refiero a esto como la diferencia entre "tipos de valores" y "tipos de objetos". Los tipos de valor representan algún valor, y es el valor Me importa, no la identidad de cada objeto individual. Dos formas diferentes de obtener el mismo valor son igualmente buenas, y el "contrato" de código que pasa alrededor de los tipos de valor generalmente solo promete darle un objeto con algún valor, sin especificar qué objeto particular es. Para los tipos de objetos OTOH, cada instancia individual tiene su propia identidad, incluso si contiene exactamente los mismos datos que otra instancia. El "contrato" de código que pasa alrededor de los tipos de objetos generalmente promete hacer un seguimiento de los objetos individuales exactos.
Entonces, ¿por qué las clases mutables incorporadas no usan su id como hash? Es porque son todos contenedores, y por lo general consideran contenedores a ser en su mayoría como los tipos de valor, con su valor determinado por los elementos contenidos:
>>> [1, 2, 3] == [1, 2, 3]
True
>>> {f: 10} == {f: 10}
True
Pero mutables contenedores tienen un valor que es transitoria. Alguna lista dada actualmente tiene el valor [1, 2, 3]
, pero se puede mutar en tener el valor [4, 5, 6]
. Si pudiera usar listas como claves del diccionario, entonces tendríamos que decidir si la búsqueda debe usar el valor (actual) de la lista o su identidad.De cualquier manera, podemos (muy) sorprendernos cuando el valor de un objeto que se está utilizando actualmente como clave de diccionario se modifique al mutarlo. El uso de objetos como claves del diccionario solo funciona bien cuando el valor del objeto es su identidad, o cuando la identidad de un objeto es irrelevante para su valor. Entonces, la respuesta elegida por Python es declarar que los contenedores mutables son inigualables.
Ahora, detalles más específicos en respuesta a preguntas directas:
1) Desde este hash predeterminada en CPython (aunque al parecer sólo < 2.6, de acuerdo con otras respuestas/comentarios) mapas en la memoria del objeto dirección, luego en CPython no hay dos objetos que usen hashing predeterminado que estén en vivo al mismo tiempo y que posiblemente entren en conflicto en sus valores de hash, independientemente de las clases involucradas (y si está almacenado como clave de diccionario está en vivo). También esperaría que otras implementaciones de Python que no usan direcciones de memoria como hash aún tengan distribuciones de hash precisas entre los objetos que usen el hashing predeterminado. Entonces sí, puedes confiar en eso.
2) Mientras no devuelva como un hash personalizado un resultado que sea exactamente el hash de algún objeto existente, debería estar relativamente bien. Tengo entendido que los contenedores basados en hash de Python son relativamente tolerantes con funciones hash subóptimas, siempre que no estén completamente degeneradas.
http://stackoverflow.com/a/2909119/174728 –
@gnibbler: tengo eso ya - vea el enlace en la pregunta – sebpiq