procedimientos - ¿Por qué y cómo son halables las funciones de Python?
lista de funciones de python (3)
No es nada especial. Como puede ver si examina el método de __hash__
del tipo de función:
>>> def f(): pass
...
>>> type(f).__hash__
<slot wrapper ''__hash__'' of ''object'' objects>
la parte of ''object'' objects
significa que simplemente hereda el object
__hash__
basado en identidad predeterminado. Función ==
y hash
funcionan por identidad. La diferencia entre id
y hash
es normal para cualquier tipo que herede el object.__hash__
:
>>> x = object()
>>> id(x)
40145072L
>>> hash(x)
2509067
Podrías pensar que __hash__
solo se debe definir para objetos inmutables, y estarías casi en lo cierto, pero le falta un detalle clave. __hash__
solo se debe definir para objetos en los que todo lo que está involucrado en las comparaciones ==
es inmutable. Para los objetos cuyo ==
se basa en la identidad, también es completamente estándar basar el hash
en la identidad, ya que incluso si los objetos son mutables, no es posible que se puedan modificar de manera que cambien su identidad. Los archivos, módulos y otros objetos mutables con identidad ==
todos se comportan de esta manera.
Recientemente probé los siguientes comandos en Python:
>>> {lambda x: 1: ''a''}
{<function __main__.<lambda>>: ''a''}
>>> def p(x): return 1
>>> {p: ''a''}
{<function __main__.p>: ''a''}
El éxito de ambas creaciones dict
indica que tanto las funciones regulares como las lambda son manejables. (Algo como {[]: ''a''}
falla con TypeError: unhashable type: ''list''
).
El hash aparentemente no es necesariamente el ID de la función:
>>> m = lambda x: 1
>>> id(m)
140643045241584
>>> hash(m)
8790190327599
>>> m.__hash__()
8790190327599
El último comando muestra que el método __hash__
está explícitamente definido para lambda
s, es decir, esto no es algo automatico que Python computa basado en el tipo.
¿Cuál es la motivación detrás de hacer las funciones hashable? Para obtener una bonificación, ¿cuál es el hash de una función?
Puede ser útil, por ejemplo, para crear conjuntos de objetos de función, o para indexar un dict por funciones. Los objetos inmutables normalmente soportan __hash__
. En cualquier caso, no hay diferencia interna entre una función definida por una def
o por una lambda
, eso es puramente sintáctico.
El algoritmo utilizado depende de la versión de Python. Parece que estás usando una versión reciente de Python en una caja de 64 bits. En ese caso, el hash de un objeto de función es la rotación correcta de su id()
en 4 bits, con el resultado visto como un entero de 64 bits con signo. El cambio a la derecha se realiza porque las direcciones de los objetos (resultados de id()
están generalmente alineadas de modo que sus últimos 3 o 4 bits siempre son 0, y esa es una propiedad ligeramente molesta para una función de hash.
En tu ejemplo específico,
>>> i = 140643045241584 # your id() result
>>> (i >> 4) | ((i << 60) & 0xffffffffffffffff) # rotate right 4 bits
8790190327599 # == your hash() result
Una función es hashable porque es un objeto normal, incorporado, no mutable.
Del manual de Python :
Un objeto es manejable si tiene un valor hash que nunca cambia durante su vida útil (necesita un
__hash__()
), y se puede comparar con otros objetos (necesita un__eq__()
o__cmp__()
). Los objetos que se pueden comparar y que son iguales deben tener el mismo valor hash.La capacidad de acceso hace que un objeto se pueda utilizar como una clave de diccionario y un miembro del conjunto, porque estas estructuras de datos usan el valor de hash internamente.
Todos los objetos incorporados inmutables de Python son lavables, mientras que no hay contenedores mutables (como listas o diccionarios). Los objetos que son instancias de clases definidas por el usuario son manejables por defecto; todos comparan desigual (excepto con ellos mismos), y su valor hash se deriva de su
id()
.