password funcion encrypt python hash tuples

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) y x.__hash__() devuelve un valor apropiado tal que x == y implica que x is y y hash(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) y x.__hash__() devuelve un valor apropiado tal que x == y implica que x is y y hash(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.