funcion - sha1 encrypt python
¿Cómo Python calcula el hash de una tupla? (4)
En Python, si tengo una tupla con muchos elementos, ¿se calcula su hash a partir de los id
sus elementos o el contenido de sus elementos?
En este ejemplo,
a = (1, [1,2])
hash(a)
Se equivoca diciendo que la lista es inestable. Así que supongo que no se calcula por ID, o probablemente hay una comprobación de si el elemento es mutable.
Ahora ve este ejemplo
class A: pass
a0 = A()
ta = (1, a0)
hash(ta) # -1122968024
a0.x = 20
hash(ta) # -1122968024
Aquí resulta que el hash de ta
no cambia con la modificación de su elemento, es decir, a0
. ¿Entonces tal vez la identificación de a0
se usa para el cálculo de hash? ¿De alguna manera se considera a0
como inmutable? ¿Cómo sabe Python si un tipo es mutable?
Ahora considera este caso
b = (1, 2)
id(b) # 3980742764
c = (1, 2)
id(c) # 3980732588
tb = (1, b)
tc = (1, c)
hash(tb) # -1383040070
hash(tc) # -1383040070
Parece que el contenido de b
y c
se utiliza para el cálculo de hash.
¿Cómo debo entender estos ejemplos?
El contrato principal de hash es que los objetos iguales tienen hashes iguales . En particular, el hashing no se preocupa directamente por la mutabilidad o mutación; solo le importa la mutación que afecta las comparaciones de igualdad .
Su primera tupla no se puede romper porque mutar la lista anidada cambiaría el comportamiento de la tupla en las comparaciones de igualdad.
Mutar a0
en su segundo ejemplo no afecta el hash de la tupla porque no afecta a las comparaciones de igualdad. a0
sigue siendo igual a sí mismo, y su hash no ha cambiado.
tb
y tc
en tu tercer ejemplo tienen hashes iguales porque son tuplas iguales, independientemente de si sus elementos son los mismos objetos.
Todo esto significa que las tuplas no pueden (directamente) usar id
para hashes. Si lo hicieran, tuplas iguales con elementos distintos pero iguales podrían hacer un hash diferente, violando el contrato de hash. Sin tipos de elementos de carcasa especial, las únicas cosas que las tuplas pueden usar para calcular sus propios hashes son los hashes de sus elementos, por lo que las tuplas basan sus hashes en los hashes de sus elementos.
El hash de una tuple
se basa en el contenido , no en los _id_s de las tuplas. Y los hashes se calculan de forma recursiva: si un elemento no es hashable (como un elemento de list
), entonces la tupla no es hashable.
Es perfectamente normal que si a
y b
sean tuplas y a == b
, entonces hash(a) == hash(b)
(si los hashes se pueden calcular, por supuesto), incluso si a is not b
.
(al contrario, hash(a) == hash(b)
no significa que a == b
)
La información que se transmite a menudo no es muy útil, debido a la internación de objetos python, por ejemplo.
La respuesta a la pregunta "¿Se calcula el hash de la tupla según la identidad o el valor?" Es ninguno.
La respuesta correcta es que el hash de la tupla se calcula a partir de los hashes de los elementos. Cómo se calculan esos hashes es (más o menos) irrelevante.
Una forma fácil de demostrar esto es ver qué sucede cuando coloca una lista en una tupla:
>>> hash( (1, 2) )
3713081631934410656
>>> hash( (1, []) )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: ''list''
Debido a que las listas no son hashables, una tupla que contiene una lista tampoco es hashable.
Veamos más de cerca este ejemplo que trajiste:
class A: pass
a0 = A()
ta = (1, a0)
hash(ta) # -1122968024
a0.x = 20
hash(ta) # -1122968024
¿Por qué la configuración a0.x = 20
afecta al hash de la tupla? Bueno, si modificamos este código para generar el hash de a0
, verás que la configuración a0.x = 20
no tiene efecto en el valor de hash de a0
:
a0 = A()
print(hash(a0)) # -9223363274645980307
a0.x = 20
print(hash(a0)) # -9223363274645980307
La razón de esto es que Python implementa una función hash predeterminada para ti. De la documentación :
Las clases definidas por el usuario tienen los
__eq__()
y__hash__()
de forma predeterminada; con ellos, todos los objetos se comparan de manera desigual (excepto con ellos mismos) yx.__hash__()
devuelve un valor apropiado tal quex == y
implica quex is y
yhash(x) == hash(y)
.
La función hash predeterminada ignora los atributos del objeto y calcula el hash basándose en la identificación del objeto. No importa qué cambios le hagas a a0
, su hash siempre se mantendrá igual. (Aunque es posible definir una función hash personalizada para instancias de su clase A
implementando un método __hash__
personalizado).
Anexo: La razón por la que las listas no son hashable es porque son mutables. De la documentación :
Si una clase define objetos mutables e implementa un
__eq__()
, no debe implementar__hash__()
, ya que la implementación de las colecciones hashable requiere que el valor hash de una clave sea inmutable (si el valor hash del objeto cambia, será incorrecto cubo de hachís).
Las listas caen en esta categoría.
Ninguno. Se calcula sobre la base de los hashes de estos elementos, no los contenidos (valores).
Eche un vistazo a este párrafo en el glosario de documentación de python .
Si algo es hashable o no, y cómo se hash, depende de la implementación de su método .__hash__()
. Python en sí mismo no tiene idea acerca de la mutabilidad de un objeto.
En su primer ejemplo, la tuple
sucede al hash en base a sus elementos, mientras que una list
no tiene ningún hash en absoluto, el método .__hash__()
no está implementado para ello (y por una buena razón). Es por eso que una tuple
con un objeto de list
dentro de ella no es hashable.
Ahora, teniendo esto en cuenta, echemos un vistazo a la documentación del modelo de datos de Python y lo que tiene que decir sobre el tema:
Las clases definidas por el usuario tienen los
__eq__()
y__hash__()
de forma predeterminada; con ellos, todos los objetos se comparan de manera desigual (excepto con ellos mismos) yx.__hash__()
devuelve un valor apropiado tal quex == y
implica quex is y
yhash(x) == hash(y)
.
Es por eso que no tiene que definir .__hash__()
para sus clases. Python lo hace por usted en este caso. Sin embargo, la implementación predeterminada no lleva los campos de instancia a la cuenta. Es por eso que puede cambiar los valores dentro de su objeto sin cambiar su hash.
En este sentido, tiene razón: la implementación predeterminada ( CPython''s ) de la función de hashing para las clases personalizadas se basa en el id()
de un objeto, y no en los valores que contiene. Es un detalle de implementación y, sin embargo, difiere entre las versiones de Python. En versiones más recientes de Python, la relación entre hash()
e id()
implica cierta aleatorización.
Pero, ¿cómo se hace en realidad el hash?
Si bien los detalles son bastante complicados y probablemente impliquen un poco de matemática avanzada, la implementación de la función hash para los objetos de la tupla está escrita en C, y se puede ver here (ver static Py_hash_t tuplehash(PyTupleObject *v)
.
El cálculo implica XORing una constante con los hashes de cada uno de los elementos de la tupla. La línea responsable del hashing de los elementos es ésta:
y = PyObject_Hash(*p++);
Entonces, para responder a tu pregunta original: hace un montón de XOR hokus-pocus con los hashes de cada uno de sus elementos . El uso o no del contenido de estos elementos depende de sus funciones hash específicas.