¿Cierre en python?

Cuando ejecuto este código, obtengo este resultado:

15 15 

Espero que la salida sea

 15 17 

pero no lo es. La pregunta es: ¿por qué?

 def make_adder_and_setter(x): def setter(n): x = n return (lambda y: x + y, setter) myadder, mysetter = make_adder_and_setter(5) print myadder(10) mysetter(7) print myadder(10) 

Python 2.x tiene una limitación de syntax que no permite capturar una variable en lectura / escritura.

La razón es que si se asigna una variable en una función, solo hay dos posibilidades:

  1. La variable es global y ha sido declarada así con global x
  2. La variable es un local de la función.

más específicamente, se descarta que la variable sea un ámbito local de un ámbito de función envolvente

Esto ha sido reemplazado en Python 3.x con la adición de una statement nonlocal . Tu código funcionaría como se esperaba en Python 3 cambiándolo a

 def make_adder_and_setter(x): def setter(n): nonlocal x x = n return (lambda y: x + y, setter) 

El tiempo de ejecución de python 2.x es capaz de manejar la lectura-escritura cerrada sobre la variable en un nivel de bytecode, sin embargo, la limitación está en la syntax que acepta el comstackdor.

Puede ver un comstackdor lisp que genera directamente el código de bytes de Python que crea un cierre de sumdor con el estado de lectura-escritura capturado al final de este video . El comstackdor puede generar un código de bytes para Python 2.x, Python 3.x o PyPy.

Si necesita un estado mutable cerrado en Python 2.x, un truco es usar una lista:

 def make_adder_and_setter(x): x = [x] def setter(n): x[0] = n return (lambda y: x[0] + y, setter) 

Está configurando una variable local x en la función setter() . La asignación a un nombre en una función lo marca como local, a menos que le diga específicamente al comstackdor de Python lo contrario.

En Python 3, puede marcar explícitamente x como no local usando la palabra clave nonlocal :

 def make_adder_and_setter(x): def setter(n): nonlocal x x = n return (lambda y: x + y, setter) 

Ahora x se marca como una variable libre y se busca en el ámbito circundante cuando se asigna.

En Python 2 no puedes marcar un Python local como tal. La única otra opción que tienes es marcar x como global . Tendrá que recurrir a trucos en los que modifique los valores contenidos en un objeto mutable que vive en el ámbito que lo rodea.

Un atributo en la función de establecimiento funcionaría, por ejemplo; setter es local al ámbito make_adder_and_setter() , los atributos de ese objeto serían visibles para cualquier cosa que tenga acceso al setter :

 def make_adder_and_setter(x): def setter(n): setter.x = n setter.x = x return (lambda y: setter.x + y, setter) 

Otro truco es usar un contenedor mutable, como una lista:

 def make_adder_and_setter(x): x = [x] def setter(n): x[0] = n return (lambda y: x[0] + y, setter) 

En ambos casos, ya no estás asignando un nombre local; el primer ejemplo utiliza la asignación de atributos en el objeto de establecimiento, el segundo altera la lista x , no se asigna a x sí.

Su función definidora interna def setter(n) define su propia variable local x . Eso oculta la otra variable x que era un parámetro de make_adder_and_setter (hace un agujero en el scope). Así que la función de establecedor no tiene ningún efecto secundario. Simplemente establece el valor de una variable local interna y sale.

Tal vez quede claro para usted si prueba el código a continuación. Hace exactamente lo mismo, solo usa el nombre z en lugar de x.

 def make_adder_and_setter(x): def setter(n): z = n return (lambda y: x + y, setter) myadder, mysetter = make_adder_and_setter(5) print myadder(10) mysetter(7) print myadder(10)