He estado leyendo mucho sobre los cierres y creo que los entiendo, pero sin empañar la imagen para mí y para los demás, espero que alguien pueda explicar los cierres de la manera más sucinta y clara posible. Estoy buscando una explicación simple que pueda ayudarme a comprender dónde y por qué querría usarlos.
python
functional-programming
closures
ciudadano conocido
fuente
fuente
nonlocal
se agregó en python 3, python 2.x no tenía cierres completos de lectura y escritura (es decir, podría leer variables cerradas, pero no cambiar sus valores)nonlocal
palabra clave en Python 2 usando un objeto mutable, por ejemplo,L = [0] \n def counter(): L[0] += 1; return L[0]
es decir, no puede cambiar el nombre (vincularlo a otro objeto) en este caso, pero puede cambiar el objeto mutable en sí mismo al que se refiere el nombre a. La lista es obligatoria porque los enteros son inmutables en Python.Es simple: una función que hace referencia a variables de un alcance contenedor, potencialmente después de que el flujo de control haya abandonado ese alcance. Ese último bit es muy útil:
>>> def makeConstantAdder(x): ... constant = x ... def adder(y): ... return y + constant ... return adder ... >>> f = makeConstantAdder(12) >>> f(3) 15 >>> g = makeConstantAdder(4) >>> g(3) 7
Tenga en cuenta que 12 y 4 han "desaparecido" dentro de f y g, respectivamente, esta característica es lo que hace que f y g sean los cierres adecuados.
fuente
constant = x
; podría hacerloreturn y + x
en la función anidada (o recibir el argumento con el nombreconstant
), y funcionaría bien; Los argumentos son capturados por el cierre no de manera diferente a los locales sin argumentos.Me gusta esta definición aproximada y sucinta :
Yo agregaría
Los decoradores que aceptan parámetros son un uso común para los cierres. Los cierres son un mecanismo de implementación común para ese tipo de "fábrica de funciones". Con frecuencia, elijo usar cierres en el Patrón de estrategia cuando la estrategia se modifica por los datos en tiempo de ejecución.
En un lenguaje que permite la definición de bloques anónima, por ejemplo, Ruby, C #, los cierres se pueden usar para implementar (lo que equivale a) nuevas estructuras de control novedosas. La falta de bloques anónimos es una de las limitaciones de los cierres en Python .
fuente
Para ser honesto, entiendo perfectamente los cierres, excepto que nunca he tenido claro qué es exactamente lo que es el "cierre" y qué tiene de "cierre". Te recomiendo que dejes de buscar cualquier lógica detrás de la elección del término.
De todos modos, aquí está mi explicación:
def foo(): x = 3 def bar(): print x x = 5 return bar bar = foo() bar() # print 5
Una idea clave aquí es que el objeto de función devuelto por foo retiene un enlace a la var local 'x' aunque 'x' ha salido del alcance y debería estar inactivo. Este gancho es para la var en sí, no solo el valor que tenía var en ese momento, por lo que cuando se llama a bar, imprime 5, no 3.
También tenga claro que Python 2.x tiene un cierre limitado: no hay forma de que pueda modificar 'x' dentro de 'bar' porque escribir 'x = bla' declararía una 'x' local en la barra, no asignaría a 'x' de foo . Este es un efecto secundario de la declaración asignación = de Python. Para evitar esto, Python 3.0 introduce la palabra clave no local:
def foo(): x = 3 def bar(): print x def ack(): nonlocal x x = 7 x = 5 return (bar, ack) bar, ack = foo() ack() # modify x of the call to foo bar() # print 7
fuente
Nunca escuché que las transacciones se usen en el mismo contexto para explicar qué es un cierre y realmente no hay semántica de transacciones aquí.
Se llama cierre porque "cierra" la variable externa (constante), es decir, no es solo una función, sino un recinto del entorno donde se creó la función.
En el siguiente ejemplo, llamar al cierre g después de cambiar x también cambiará el valor de x dentro de g, ya que g cierra sobre x:
x = 0 def f(): def g(): return x * 2 return g closure = f() print(closure()) # 0 x = 2 print(closure()) # 4
fuente
g()
calculax * 2
pero no devuelve nada. Eso debería serreturn x * 2
. +1 sin embargo para una explicación de la palabra "cierre".Aquí hay un caso de uso típico para cierres: devoluciones de llamada para elementos de GUI (esta sería una alternativa a la subclasificación de la clase de botón). Por ejemplo, puede construir una función que se llamará en respuesta a la presión de un botón y "cerrar" sobre las variables relevantes en el ámbito principal que son necesarias para procesar el clic. De esta manera, puede conectar interfaces bastante complicadas desde la misma función de inicialización, construyendo todas las dependencias en el cierre.
fuente
En Python, un cierre es una instancia de una función que tiene variables vinculadas a ella de manera inmutable.
De hecho, el modelo de datos explica esto en su descripción del
__closure__
atributo de funciones :Para demostrar esto:
def enclosure(foo): def closure(bar): print(foo, bar) return closure closure_instance = enclosure('foo')
Claramente, sabemos que ahora tenemos una función apuntada desde el nombre de la variable
closure_instance
. Aparentemente, si lo llamamos con un objeto,bar
debería imprimir la cadena,'foo'
y cualquiera que sea la representación de la cadenabar
.De hecho, la cadena 'foo' está vinculada a la instancia de la función, y podemos leerla directamente aquí, accediendo al
cell_contents
atributo de la primera (y única) celda en la tupla del__closure__
atributo:>>> closure_instance.__closure__[0].cell_contents 'foo'
Además, los objetos de celda se describen en la documentación de la API de C:
Y podemos demostrar el uso de nuestro cierre, notando que
'foo'
está atascado en la función y no cambia:>>> closure_instance('bar') foo bar >>> closure_instance('baz') foo baz >>> closure_instance('quux') foo quux
Y nada puede cambiarlo:
>>> closure_instance.__closure__ = None Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: readonly attribute
Funciones parciales
El ejemplo dado usa el cierre como una función parcial, pero si este es nuestro único objetivo, el mismo objetivo se puede lograr con
functools.partial
>>> from __future__ import print_function # use this if you're in Python 2. >>> partial_function = functools.partial(print, 'foo') >>> partial_function('bar') foo bar >>> partial_function('baz') foo baz >>> partial_function('quux') foo quux
También hay cierres más complicados que no encajarían en el ejemplo de función parcial, y los demostraré más a medida que el tiempo lo permita.
fuente
# A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory. # Defining a closure # This is an outer function. def outer_function(message): # This is an inner nested function. def inner_function(): print(message) return inner_function # Now lets call the outer function and return value bound to name 'temp' temp = outer_function("Hello") # On calling temp, 'message' will be still be remembered although we had finished executing outer_function() temp() # Technique by which some data('message') that remembers values in enclosing scopes # even if they are not present in memory is called closures # Output: Hello
Los criterios que deben cumplir los cierres son:
# Example 2 def make_multiplier_of(n): # Outer function def multiplier(x): # Inner nested function return x * n return multiplier # Multiplier of 3 times3 = make_multiplier_of(3) # Multiplier of 5 times5 = make_multiplier_of(5) print(times5(3)) # 15 print(times3(2)) # 6
fuente
Aquí hay un ejemplo de cierres de Python3
def closure(x): def counter(): nonlocal x x += 1 return x return counter; counter1 = closure(100); counter2 = closure(200); print("i from closure 1 " + str(counter1())) print("i from closure 1 " + str(counter1())) print("i from closure 2 " + str(counter2())) print("i from closure 1 " + str(counter1())) print("i from closure 1 " + str(counter1())) print("i from closure 1 " + str(counter1())) print("i from closure 2 " + str(counter2())) # result i from closure 1 101 i from closure 1 102 i from closure 2 201 i from closure 1 103 i from closure 1 104 i from closure 1 105 i from closure 2 202
fuente
Para mí, los "cierres" son funciones que son capaces de recordar el entorno en el que fueron creados. Esta funcionalidad, te permite usar variables o métodos dentro del cierre que, de otra manera, no podrías usar porque ya no existen o están fuera de alcance por alcance. Veamos este código en ruby:
def makefunction (x) def multiply (a,b) puts a*b end return lambda {|n| multiply(n,x)} # => returning a closure end func = makefunction(2) # => we capture the closure func.call(6) # => Result equal "12"
funciona incluso cuando ambos, el método "multiplicar" y la variable "x", ya no existen. Todo por la capacidad de cierre para recordar.
fuente
todos hemos usado Decoradores en Python. Son buenos ejemplos para mostrar qué son las funciones de cierre en Python.
class Test(): def decorator(func): def wrapper(*args): b = args[1] + 5 return func(b) return wrapper @decorator def foo(val): print val + 2 obj = Test() obj.foo(5)
aquí el valor final es 12
Aquí, la función de envoltorio puede acceder al objeto func porque el envoltorio es "cierre léxico", puede acceder a sus atributos principales. Por eso, puede acceder al objeto func.
fuente
Me gustaría compartir mi ejemplo y una explicación sobre los cierres. Hice un ejemplo de Python y dos figuras para demostrar los estados de la pila.
def maker(a, b, n): margin_top = 2 padding = 4 def message(msg): print('\n’ * margin_top, a * n, ' ‘ * padding, msg, ' ‘ * padding, b * n) return message f = maker('*', '#', 5) g = maker('', '♥’, 3) … f('hello') g(‘good bye!')
La salida de este código sería la siguiente:
***** hello ##### good bye! ♥♥♥
Aquí hay dos figuras para mostrar las pilas y el cierre adjunto al objeto de función.
cuando la función es devuelta por el fabricante
cuando la función se llama más tarde
Cuando se llama a la función a través de un parámetro o una variable no local, el código necesita vinculaciones de variables locales como margin_top, padding y a, b, n. Para garantizar que el código de la función funcione, el marco de pila de la función maker que desapareció hace mucho tiempo debería ser accesible, que está respaldado en el cierre que podemos encontrar junto con el objeto de función del mensaje.
fuente
La mejor explicación que he visto de un cierre fue explicar el mecanismo. Fue algo como ésto:
Imagine su pila de programas como un árbol degenerado donde cada nodo tiene solo un hijo y el nodo de hoja única es el contexto de su procedimiento en ejecución.
Ahora relaje la restricción de que cada nodo solo puede tener un hijo.
Si hace esto, puede tener una construcción ('rendimiento') que puede regresar de un procedimiento sin descartar el contexto local (es decir, no lo saca de la pila cuando regresa). La próxima vez que se invoca el procedimiento, la invocación retoma el antiguo marco de pila (árbol) y continúa ejecutándose donde lo dejó.
fuente