He visto y usado funciones anidadas en Python, y coinciden con la definición de un cierre. Entonces, ¿por qué se les llama en nested functions
lugar de closures
?
¿Las funciones anidadas no son cierres porque no las utiliza el mundo externo?
ACTUALIZACIÓN: Estaba leyendo sobre cierres y me hizo pensar en este concepto con respecto a Python. Busqué y encontré el artículo mencionado por alguien en un comentario a continuación, pero no pude entender completamente la explicación en ese artículo, por eso estoy haciendo esta pregunta.
python
closures
nested-function
Srikar Appalaraju
fuente
fuente
Respuestas:
Un cierre se produce cuando una función tiene acceso a una variable local desde un ámbito de cierre que ha finalizado su ejecución.
Cuando
make_printer
se llama, se coloca un nuevo marco en la pila con el código compilado para laprinter
función como una constante y el valor demsg
como local. Luego crea y devuelve la función. Debido a que la función haceprinter
referencia a lamsg
variable, se mantiene viva después de que lamake_printer
función ha regresado.Entonces, si sus funciones anidadas no
entonces no son cierres.
Aquí hay un ejemplo de una función anidada que no es un cierre.
Aquí, estamos vinculando el valor al valor predeterminado de un parámetro. Esto ocurre cuando
printer
se crea la función y, por lo tanto, no es necesario mantener una referencia al valor demsg
externoprinter
después de losmake_printer
retornos.msg
es solo una variable local normal de la funciónprinter
en este contexto.fuente
self
. (En JavaScript / Python eso es casi cierto.)i
] desde un ámbito de inclusión". se refiere, es decir, puede inspeccionar (o cambiar)i
el valor, incluso si / cuando ese alcance "ha terminado su ejecución", es decir, la ejecución de un programa ha pasado a otras partes del código. El bloque dondei
está definido ya no existe, pero las funciones a las quei
se hace referencia todavía pueden hacerlo. Esto se describe comúnmente como "cierre sobre la variablei
". Para no tratar con las variables específicas, se puede implementar como cierre sobre todo el marco del entorno donde se define esa variable.La pregunta ya ha sido respondida por aaronasterling
Sin embargo, alguien podría estar interesado en cómo se almacenan las variables bajo el capó.
Antes de llegar al fragmento:
Los cierres son funciones que heredan variables de su entorno envolvente. Cuando pasa una devolución de llamada de función como argumento a otra función que hará E / S, esta función de devolución de llamada se invocará más tarde, y esta función, casi mágicamente, recordará el contexto en el que se declaró, junto con todas las variables disponibles en ese contexto
Si una función no utiliza variables libres, no forma un cierre.
Si hay otro nivel interno que utiliza variables libres, todos los niveles anteriores guardan el entorno léxico (ejemplo al final)
Los atributos de función
func_closure
en python <3.X o__closure__
en python> 3.X guardan las variables libres.Cada función en python tiene los atributos de este cierre, pero no guarda ningún contenido si no hay variables libres.
ejemplo: de atributos de cierre pero sin contenido dentro ya que no hay variable libre.
NB: LA VARIABLE GRATUITA DEBE CREAR UN CIERRE.
Explicaré usando el mismo fragmento que el anterior:
Y todas las funciones de Python tienen un atributo de cierre, así que examinemos las variables de cierre asociadas con una función de cierre.
Aquí está el atributo
func_closure
para la funciónprinter
El
closure
atributo devuelve una tupla de objetos de celda que contienen detalles de las variables definidas en el ámbito de inclusión.El primer elemento en el func_closure que podría ser None o una tupla de celdas que contienen enlaces para las variables libres de la función y es de solo lectura.
Aquí, en el resultado anterior, puede ver
cell_contents
, veamos qué almacena:Entonces, cuando llamamos a la función
printer()
, accede al valor almacenado dentro decell_contents
. Así es como obtuvimos la salida como 'Foo!'Nuevamente, explicaré el uso del fragmento anterior con algunos cambios:
En el fragmento anterior, no imprimo msg dentro de la función de impresora, por lo que no crea ninguna variable libre. Como no hay una variable libre, no habrá contenido dentro del cierre. Eso es exactamente lo que vemos arriba.
Ahora explicaré otro fragmento diferente para aclarar todo
Free Variable
conClosure
:Entonces, vemos que una
func_closure
propiedad es una tupla de celdas de cierre , podemos referirlas explícitamente y su contenido: una celda tiene la propiedad "cell_contents"Aquí, cuando llamamos
inn
, referirá todas las variables libres de guardado para que obtengamosI am free variable
fuente
func_closure
ahora se llama__closure__
, de manera similar a los otrosfunc_*
atributos.__closure_
está disponible en Python 2.6+ para compatibilidad con Python 3.__closure__
objeto el cierre.Python tiene un soporte débil para el cierre. Para ver a qué me refiero, tome el siguiente ejemplo de un contador que usa cierre con JavaScript:
El cierre es bastante elegante ya que le da a las funciones escritas así la capacidad de tener "memoria interna". A partir de Python 2.7 esto no es posible. Si intentas
Obtendrá un error que dice que x no está definido. Pero, ¿cómo puede ser eso si otros han demostrado que puedes imprimirlo? Esto se debe a cómo Python administra el alcance variable de las funciones. Si bien la función interna puede leer las variables de la función externa, no puede escribirlas .
Esto es una pena realmente. Pero con solo el cierre de solo lectura, al menos puede implementar el patrón decorador de funciones para el cual Python ofrece azúcar sintáctico.
Actualizar
Como se ha señalado, hay formas de lidiar con las limitaciones del alcance de Python y expondré algunas.
1. Use la
global
palabra clave (en general no se recomienda).2. En Python 3.x, use la
nonlocal
palabra clave (sugerida por @unutbu y @leewz)3. Definir una clase modificable simple
Object
y crea un
Object scope
insideinitCounter
para almacenar las variablesComo
scope
realmente es solo una referencia, las acciones tomadas con sus campos no se modifican realmentescope
, por lo que no surge ningún error.4. Una forma alternativa, como señaló @unutbu, sería definir cada variable como una matriz (
x = [0]
) y modificar su primer elemento (x[0] += 1
). Nuevamente, no surge ningún error porquex
no se modifica por sí mismo.5. Según lo sugerido por @raxacoricofallapatorius, puede hacer
x
una propiedad decounter
fuente
x = [0]
en el ámbito externo y utilizarx[0] += 1
en el ámbito interno. En Python3, puede mantener su código tal como está y usar la palabra clave no local .x
apunta la variable permanece exactamente igual incluso si llamainc()
o lo que sea, y no escribió efectivamente en la variable.x
una propiedad decounter
.nonlocal
palabra clave, que es comoglobal
pero para las variables de una función externa. Esto permitirá que una función interna vuelva a vincular un nombre de sus funciones externas. Creo que "vincular al nombre" es más preciso que "modificar la variable".Python 2 no tenía cierres, tenía soluciones que parecían cierres.
Hay muchos ejemplos en las respuestas ya dadas: copiar variables en la función interna, modificar un objeto en la función interna, etc.
En Python 3, el soporte es más explícito y sucinto:
Uso:
La
nonlocal
palabra clave vincula la función interna a la variable externa mencionada explícitamente, de hecho, la encierra. Por lo tanto, más explícitamente un 'cierre'.fuente
Tuve una situación en la que necesitaba un espacio de nombre separado pero persistente. Usé clases Yo no de otra manera. Los nombres segregados pero persistentes son cierres.
fuente
Da:
Este es un ejemplo de qué es un cierre y cómo se puede usar.
fuente
Me gustaría ofrecer otra comparación simple entre Python y JS, si esto ayuda a aclarar las cosas.
JS:
y ejecutando:
Pitón:
y ejecutando:
Motivo: Como muchos otros dijeron anteriormente, en python, si hay una asignación en el ámbito interno a una variable con el mismo nombre, se crea una nueva referencia en el ámbito interno. No es así con JS, a menos que declare explícitamente uno con la
var
palabra clave.fuente