example - ¿Por qué las funciones anidadas de Python no se llaman cierres?
decorators python tutorial (7)
He visto y usado funciones anidadas en Python, y coinciden con la definición de un cierre. Entonces, ¿por qué se llaman nested functions
lugar de closures
?
¿Las funciones anidadas no se cierran porque no son utilizadas por 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 es por lo que estoy haciendo esta pregunta.
Me gustaría ofrecer otra comparación simple entre Python y JS, si esto ayuda a aclarar las cosas.
JS:
function make () {
var cl = 1;
function gett () {
console.log(cl);
}
function sett (val) {
cl = val;
}
return [gett, sett]
}
y ejecutando:
a = make(); g = a[0]; s = a[1];
s(2); g(); // 2
s(3); g(); // 3
Pitón:
def make ():
cl = 1
def gett ():
print(cl);
def sett (val):
cl = val
return gett, sett
y ejecutando:
g, s = make()
g() #1
s(2); g() #1
s(3); g() #1
Motivo: Como muchos otros han dicho 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 palabra clave var
.
Python 2 no tenía cierres, tenía soluciones que parecían cierres.
Hay muchos ejemplos en las respuestas ya dadas: copiar en variables a la función interna, modificar un objeto en la función interna, etc.
En Python 3, el soporte es más explícito y conciso:
def closure():
count = 0
def inner():
nonlocal count
count += 1
print(count)
return inner
Uso:
start = closure()
start() # prints 1
start() # prints 2
start() # prints 3
La palabra clave nonlocal
vincula la función interna a la variable externa explícitamente mencionada, en efecto, la encierra. De ahí más explícitamente un ''cierre''.
Python tiene un soporte débil para el cierre. Para ver lo que quiero decir, tome el siguiente ejemplo de un contador que utiliza el cierre con JavaScript:
function initCounter(){
var x = 0;
function counter () {
x += 1;
console.log(x);
};
return counter;
}
count = initCounter();
count(); //Prints 1
count(); //Prints 2
count(); //Prints 3
El cierre es bastante elegante ya que le da a las funciones escritas como esta la capacidad de tener "memoria interna". A partir de Python 2.7 esto no es posible. Si intentas
def initCounter():
x = 0;
def counter ():
x += 1 ##Error, x not defined
print x
return counter
count = initCounter();
count(); ##Error
count();
count();
Obtendrá un error que dice que x no está definido. Pero, ¿cómo puede ser eso si otros han demostrado que puede imprimirlo? Esto se debe a la forma en que Python administra el alcance de la variable de funciones. Mientras que la función interna puede leer las variables de la función externa, no puede escribirlas .
Esto es realmente una vergüenza. Pero con solo el cierre de solo lectura, al menos puede implementar el patrón de decoración de función para el cual Python ofrece azúcar sintáctica.
Actualizar
Como se ha señalado, hay formas de lidiar con las limitaciones del alcance de Python y las expondré.
1. Utilice la palabra clave global
(en general no se recomienda).
2. Definir un Object
clase modificable simple.
class Object(object):
pass
y crea un Object scope
dentro de initCounter
para almacenar las variables
def initCounter ():
scope = Object()
scope.x = 0
def counter():
scope.x += 1
print scope.x
return counter
Dado que el scope
es solo una referencia, las acciones tomadas con sus campos no modifican el scope
sí, por lo que no surge ningún error.
3. 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
). De nuevo, no surge ningún error porque x
no se modifica.
4. Según lo sugerido por @raxacoricofallapatorius, podrías hacer de x
una propiedad de counter
def initCounter ():
def counter():
counter.x += 1
print counter.x
counter.x = 0
return counter
Tuve una situación en la que necesitaba un espacio de nombres separado pero persistente. Utilicé clases. No lo contrario. Los nombres segregados pero persistentes son cierres.
>>> class f2:
... def __init__(self):
... self.a = 0
... def __call__(self, arg):
... self.a += arg
... return(self.a)
...
>>> f=f2()
>>> f(2)
2
>>> f(2)
4
>>> f(4)
8
>>> f(8)
16
# **OR**
>>> f=f2() # **re-initialize**
>>> f(f(f(f(2)))) # **nested**
16
# handy in list comprehensions to accumulate values
>>> [f(i) for f in [f2()] for i in [2,2,4,8]][-1]
16
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.
def make_printer(msg):
def printer():
print msg
return printer
printer = make_printer(''Foo!'')
printer()
Cuando se llama a make_printer
se make_printer
un nuevo marco en la pila con el código compilado para la función de printer
como una constante y el valor de msg
como local. A continuación, crea y devuelve la función. Debido a que la función de printer
referencia a la variable msg
, se mantiene activa después de que la función make_printer
haya regresado.
Entonces, si tus funciones anidadas no lo hacen
- Accede a las variables que son locales para encerrar los ámbitos,
- hacerlo cuando se ejecutan fuera de ese alcance,
Entonces no son cierres.
Aquí hay un ejemplo de una función anidada que no es un cierre.
def make_printer(msg):
def printer(msg=msg):
print msg
return printer
printer = make_printer("Foo!")
printer() #Output: Foo!
Aquí, estamos vinculando el valor al valor predeterminado de un parámetro. Esto ocurre cuando se crea la función de printer
y, por lo tanto, no es necesario mantener ninguna referencia al valor de msg
externo a la printer
después de que retorna make_printer
. msg
es solo una variable local normal de la printer
funciones en este contexto.
La pregunta ya ha sido respondida por
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 transfiere una función de devolución de llamada como un 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 recordará, casi mágicamente, el contexto en el que se declaró, junto con todas las variables disponibles. en ese contexto.
Si una función no usa 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 la función
func_closure
en python <3.X o__closure__
en python> 3.X guardan las variables libres.Cada función en Python tiene estos atributos de cierre, pero no guarda ningún contenido si no hay variables libres.
Ejemplo: de los atributos de cierre pero sin contenido dentro ya que no hay variable libre.
>>> def foo():
... def fii():
... pass
... return fii
...
>>> f = foo()
>>> f.func_closure
>>> ''func_closure'' in dir(f)
True
>>>
NB: VARIABLE LIBRE DEBE CREAR UN CIERRE.
Explicaré usando el mismo fragmento que arriba:
>>> def make_printer(msg):
... def printer():
... print msg
... return printer
...
>>> printer = make_printer(''Foo!'')
>>> printer() #Output: Foo!
Y todas las funciones de Python tienen un atributo de cierre, así que examinemos las variables adjuntas asociadas con una función de cierre.
Aquí está el atributo func_closure
para la printer
funciones.
>>> ''func_closure'' in dir(printer)
True
>>> printer.func_closure
(<cell at 0x108154c90: str object at 0x108151de0>,)
>>>
El atributo de closure
devuelve una tupla de objetos de celda que contienen detalles de las variables definidas en el ámbito que lo contiene.
El primer elemento del func_closure que podría ser Ninguno o una tupla de celdas que contienen enlaces para las variables libres de la función y es de solo lectura.
>>> dir(printer.func_closure[0])
[''__class__'', ''__cmp__'', ''__delattr__'', ''__doc__'', ''__format__'', ''__getattribute__'',
''__hash__'', ''__init__'', ''__new__'', ''__reduce__'', ''__reduce_ex__'', ''__repr__'',
''__setattr__'', ''__sizeof__'', ''__str__'', ''__subclasshook__'', ''cell_contents'']
>>>
Aquí, en la salida anterior, puedes ver cell_contents
, veamos qué almacena:
>>> printer.func_closure[0].cell_contents
''Foo!''
>>> type(printer.func_closure[0].cell_contents)
<type ''str''>
>>>
Entonces, cuando llamamos a la función printer()
, accede al valor almacenado dentro de los cell_contents
. Así es como obtuvimos la salida como ''Foo!''
Una vez más explicaré usando el fragmento anterior con algunos cambios:
>>> def make_printer(msg):
... def printer():
... pass
... return printer
...
>>> printer = make_printer(''Foo!'')
>>> printer.func_closure
>>>
En el fragmento de código anterior, no imprimo msg dentro de la función de la 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 de código diferente para borrar todo Free Variable
with Closure
:
>>> def outer(x):
... def intermediate(y):
... free = ''free''
... def inner(z):
... return ''%s %s %s %s'' % (x, y, free, z)
... return inner
... return intermediate
...
>>> outer(''I'')(''am'')(''variable'')
''I am free variable''
>>>
>>> inter = outer(''I'')
>>> inter.func_closure
(<cell at 0x10c989130: str object at 0x10c831b98>,)
>>> inter.func_closure[0].cell_contents
''I''
>>> inn = inter(''am'')
Entonces, vemos que una propiedad func_closure
es una tupla de celdas de cierre, podemos referirlas y sus contenidos explícitamente - una celda tiene la propiedad "cell_contents"
>>> inn.func_closure
(<cell at 0x10c9807c0: str object at 0x10c9b0990>,
<cell at 0x10c980f68: str object at 0x10c9eaf30>,
<cell at 0x10c989130: str object at 0x10c831b98>)
>>> for i in inn.func_closure:
... print i.cell_contents
...
free
am
I
>>>
Aquí cuando llamamos inn
, se referirán todas las variables de guardar libres para que obtengamos I am free variable
>>> inn(''variable'')
''I am free variable''
>>>
def nested1(num1):
print "nested1 has",num1
def nested2(num2):
print "nested2 has",num2,"and it can reach to",num1
return num1+num2 #num1 referenced for reading here
return nested2
Da:
In [17]: my_func=nested1(8)
nested1 has 8
In [21]: my_func(5)
nested2 has 5 and it can reach to 8
Out[21]: 13
Este es un ejemplo de lo que es un cierre y cómo puede usarse.