python - __eq__ - ¿Qué sucede si el objeto__hash__ cambia?
python hash function (2)
Hay un gran post en Github al respecto: qué sucede cuando te metes con el hash . Primero, debe saber que Python espera (citado en el artículo):
El hash de un objeto no cambia a lo largo de la vida útil del objeto (en otras palabras, un objeto hashable debe ser inmutable).
a == b
implicahash(a) == hash(b)
(tenga en cuenta que es posible que no se cumpla lo contrario en el caso de una colisión de hash).
Aquí está el ejemplo de código que muestra el problema de un hash variante, con un ejemplo de clase ligeramente diferente, pero la idea sigue siendo la misma:
>>> class Bad(object):
... def __init__(self, arg):
... self.arg = arg
... def __hash__(self):
... return hash(self.arg)
...
>>> Bad(1)
<__main__.Bad object at ...>
>>> hash(Bad(1))
1
>>> a = Bad(1)
>>> b = {a:1}
>>> a.arg = 2
>>> hash(a)
2
>>> b[a]
Traceback (most recent call last):
...
KeyError: <__main__.Bad object at ...>
Aquí, cambiamos implícitamente el hash de a mediante la mutación del argumento de a que se utiliza para calcular el hash. Como resultado, el objeto ya no se encuentra en un diccionario, que utiliza el hash para encontrar el objeto.
Tenga en cuenta que Python no me impide hacer esto. Podría hacerlo si quiero, haciendo que
__setattr__
AttributeError
, pero incluso así podría cambiarlo forzosamente modificando el__dict__
del objeto. Esto es lo que se quiere decir cuando decimos que Python es un lenguaje de "adultos que consienten".
No hará que Python se bloquee, pero ocurrirá un comportamiento inesperado con dict / set y todo basado en el hash del objeto.
En Python, sé que el valor que devuelve __hash__
para un objeto dado se supone que es el mismo durante toda la vida útil de ese objeto. Pero, por curiosidad, ¿qué pasa si no lo es? ¿Qué tipo de estragos causaría este hecho?
class BadIdea(object):
def __hash__(self):
return random.randint(0, 10000)
Sé que __contains__
y __getitem__
se comportaría de manera extraña, y los dictados y los conjuntos actuarían de manera extraña debido a eso. También puede terminar con valores "huérfanos" en el dict / set.
¿Qué más podría pasar? ¿Podría estrellar el intérprete, o corromper las estructuras internas?
Tu principal problema sería sin duda con los dicts y sets. Si inserta un objeto en un dict / set, y el hash de ese objeto cambia, entonces, cuando intente recuperar ese objeto, terminará buscando en un lugar diferente en la matriz subyacente del dict / set y, por lo tanto, no encontrará el objeto. Esta es precisamente la razón por la que las claves dict siempre deben ser inmutables.
Aquí hay un pequeño ejemplo: digamos que colocamos o
en un dict, y el hash inicial de o es 3. Haríamos algo como esto (una ligera simplificación, pero es claro):
Hash table: 0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | | | | o | | | | | +---+---+---+---+---+---+---+---+ ^ we put o here, since it hashed to 3
Ahora digamos que el hash de o
cambia a 6
. Si queremos recuperar o
del dictado, veremos el punto 6
, ¡pero no hay nada allí! Esto causará un falso negativo al consultar la estructura de datos. En realidad, cada elemento de la matriz anterior podría tener un "valor" asociado en el caso de un dict, y podría haber múltiples elementos en un solo punto (por ejemplo, una colisión de hash ). Además, generalmente tomamos el valor de hash de módulo del tamaño de la matriz al decidir dónde colocar el elemento. Sin embargo, independientemente de todos estos detalles, el ejemplo anterior aún transmite con precisión lo que podría salir mal cuando cambia el código hash de un objeto.
¿Podría estrellar el intérprete, o corromper las estructuras internas?
No, esto no va a pasar. Cuando decimos que el cambio de hash de un objeto es "peligroso", nos referimos a peligroso en el sentido de que esencialmente anula el propósito del hash y hace que el código sea difícil, si no imposible, de razonar. No queremos decir peligroso en el sentido de que podría causar choques.