¿Por qué y cómo son hashable las funciones de Python?

Recientemente probé los siguientes comandos en Python:

>>> {lambda x: 1: 'a'} {<function __main__.>: 'a'} >>> def p(x): return 1 >>> {p: 'a'} {: 'a'} 

El éxito de ambas creaciones de dict indica que tanto lambda como las funciones regulares son hashable. (Algo así 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á definido explícitamente para lambda s, es decir, esto no es una cosa automática que Python calcula en función del tipo.

¿Cuál es la motivación detrás de hacer hashable funciones? Para una bonificación, ¿cuál es el hash de una función?

    No es nada especial. Como puede ver si examina el método __hash__ no __hash__ del tipo de función:

     >>> def f(): pass ... >>> type(f).__hash__  

    la parte of 'object' objects significa que simplemente hereda el __hash__ de object basado en identidad predeterminado. Función == y trabajo hash 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ía pensar que __hash__ solo se debe definir para objetos inmutables, y casi tendría razón, pero le falta un detalle clave. __hash__ solo debe definirse para los objetos donde todo lo 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 pueden ser mutables de una manera que pueda cambiar su identidad. Los archivos, módulos y otros objetos mutables con == basado en identidad se comportan de esta manera.

    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() ) generalmente están alineadas, de modo que sus últimos 3 o 4 bits son siempre 0, y esa es una propiedad ligeramente molesta para una función hash.

    En su 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 hashable si tiene un valor hash que nunca cambia durante su vida útil (necesita un __hash__() ) y puede compararse con otros objetos (necesita un __eq__() o __cmp__() ). Los objetos hash que comparen iguales deben tener el mismo valor hash.

    La capacidad de hash hace que un objeto se pueda utilizar como clave de diccionario y como miembro de conjunto, porque estas estructuras de datos utilizan el valor de hash internamente.

    Todos los objetos inmutables incorporados de Python son hashable, mientras que no hay contenedores mutables (como listas o diccionarios). Los objetos que son instancias de clases definidas por el usuario son hashable por defecto; todos ellos comparan la desigualdad (excepto con ellos mismos), y su valor hash se deriva de su id() .