2010-10-26 25 views
192

He visto y utilizado funciones anidadas en Python, y coinciden con la definición de un cierre. Entonces, ¿por qué se llaman nested functions en lugar de closures?¿Por qué las funciones anidadas de Python no se llaman cierres?

¿Las funciones anidadas no son cierres 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, así que es por eso que estoy haciendo esta pregunta.

+8

Curiosamente, algunos google me encontraron esto, con fecha de diciembre de 2006: http://effbot.org/zone/closure.htm. No estoy seguro, ¿los "duplicados externos" están mal visto en SO? – hbw

+1

[PEP 227 - Ámbitos anidados estáticamente] (http://www.python.org/dev/peps/pep-0227/) para obtener más información. –

Respuesta

305

Un cierre se produce cuando una función tiene acceso a una variable local desde un ámbito adjunto que ha finalizado su ejecución.

def make_printer(msg): 
    def printer(): 
     print msg 
    return printer 

printer = make_printer('Foo!') 
printer() 

Cuando make_printer se llama, una nueva trama se pone en la pila con el código compilado para la función printer como una constante y el valor de msg como local. Luego crea y devuelve la función. Como la función printer hace referencia a la variable msg, se mantiene activa después de que la función make_printer haya regresado.

tanto, si sus funciones anidadas no lo hacen

  1. las variables de acceso que son locales en ámbitos circundantes,
  2. hacerlo cuando se ejecutan fuera de ese ámbito,

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 con el valor predeterminado de un parámetro. Esto ocurre cuando se crea la función printer y, por lo tanto, no es necesario hacer referencia al valor de msg externo a printer después de make_printer. msg es simplemente una variable local normal de la función printer en este contexto.

+1

Su respuesta es mucho mejor que la mía, usted hace una buena observación, pero si vamos a seguir las definiciones más estrictas de programación funcional, ¿sus ejemplos son funciones? Ha pasado un tiempo, y no puedo recordar si la programación funcional estricta permite funciones que no devuelven valores. El punto es irrelevante, si consideramos que el valor de retorno es Ninguno, pero ese es un tema completamente distinto. – mikerobi

+5

@mikerobi, no estoy seguro de que tengamos que tener en cuenta la programación funcional ya que python no es realmente un lenguaje funcional, aunque ciertamente puede usarse como tal. Pero, no, las funciones internas no son funciones en ese sentido, ya que su objetivo es crear efectos secundarios. Sin embargo, es fácil crear una función que ilustre los puntos igualmente bien, – aaronasterling

+19

@mikerobi: Si un blob de código es o no un cierre depende de si se cierra o no sobre su entorno, no como usted lo llama. Podría ser una rutina, función, procedimiento, método, bloque, subrutina, lo que sea. En Ruby, los métodos no pueden ser cierres, solo los bloques pueden. En Java, los métodos no pueden ser cierres, pero las clases pueden. Eso no los hace menos de un cierre. (Aunque el hecho de que solo cierran sobre * algunas * variables, y no pueden modificarlas, las vuelve inútiles). Podría argumentar que un método es solo un procedimiento cerrado sobre 'self'. (En JavaScript/Python eso es casi verdad.) –

79

La pregunta ya ha sido respondida poraaronasterling

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 las 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 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 interior que utiliza variables libres - todos niveles anteriores salvar el medio ambiente lexical (ejemplo al final)

  • función atributos func_closure en pitón < 3.X o __closure__ en python> 3.X guarda las variables gratuitas.

  • Cada función en python tiene estos atributos de cierre, pero no guarda ningún contenido si no hay variables libres.

ejemplo: de atributos de cierre, pero no el contenido en el interior ya que no hay variable libre.

NB: FREE VARIABLE es necesidad CREAR UN CIERRE.

explicaré usando el mismo que el anterior fragmento:

>>> 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 cierre atributo de modo que vamos a examinar las variables de cerramiento asociados con una función de cierre.

Aquí es el atributo func_closure para la función printer

>>> 'func_closure' in dir(printer) 
True 
>>> printer.func_closure 
(<cell at 0x108154c90: str object at 0x108151de0>,) 
>>> 

El atributo closure devuelve una tupla de objetos celulares que contienen detalles de las variables definidas en el ámbito circundante.

El primer elemento en el 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 se puede ver cell_contents, vamos a ver lo que almacena:

>>> printer.func_closure[0].cell_contents 
'Foo!'  
>>> type(printer.func_closure[0].cell_contents) 
<type 'str'> 
>>> 

Así, cuando llamamos a la función printer(), se accede al valor almacenado en el interior del cell_contents. Así es como obtuvimos la salida como '¡Foo!'

Una vez más voy a explicar con 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 anterior, Me di no msg de impresión 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 voy a explicar otro fragmento diferente para limpiar todo lo Free Variable con 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') 

Por lo tanto, vemos que una propiedad func_closure es una tupla de cierre células, podemos referirnos a ellos y sus contenidos explícita - una célula tiene "cell_contents" propiedad

>>> 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á todo el guardar varia libre bles por lo que tenemos I am free variable

>>> inn('variable') 
'I am free variable' 
>>> 
+6

En Python 3, 'func_closure' ahora se llama' __closure__', de manera similar a los otros atributos 'func_ *'. – lvc

+0

Gracias por la corrección. –

+2

También '__closure_' está disponible en Python 2.6+ para compatibilidad con Python 3. – Pierre

5
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 se puede utilizar.

58

Python tiene un débil soporte para el cierre. Para ver lo que quiero decir tomar el siguiente ejemplo de un contador con 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 

cierre es bastante elegante, ya que da funciones escritas así la capacidad de tener "memoria interna". A partir de Python 2.7 esto no es posible. Si prueba

def initCounter(): 
    x = 0; 
    def counter(): 
     x += 1 ##Error, x not defined 
     print x 
    return counter 

count = initCounter(); 

count(); ##Error 
count(); 
count(); 

Aparecerá 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 la forma en que Python maneja el alcance de la variable de funciones. Mientras que la función interna puede leer las variables de la función externa, no puede escribir ellos.

Esto es una pena realmente. Pero con solo un cierre de solo lectura puede implementar al menos el function decorator pattern para el cual Python ofrece azúcar sintáctica.

actualización

Como su ha señalado, hay maneras de hacer frente a las limitaciones de alcance de Python y voy a exponer algunas.

1. Utilice la palabra clave global (en general, no recomendado).

2. definir una clase simple modificable Object

class Object(object): 
    pass 

y crear un Object scope dentro initCounter para almacenar las variables

def initCounter(): 
    scope = Object() 
    scope.x = 0 
    def counter(): 
     scope.x += 1 
     print scope.x 

    return counter 

Desde scope en realidad es sólo una referencia, las acciones tomadas con su los campos realmente no modifican scope, por lo que no se produce 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 se produce ningún error porque x no se modifica.

4. Como sugiere @raxacoricofallapatorius, usted podría hacer x una propiedad de counter

def initCounter(): 

    def counter(): 
     counter.x += 1 
     print counter.x 

    counter.x = 0 
    return counter 
+20

formas de evitar esto. En Python2, podría hacer 'x = [0]' en el ámbito externo, y usar 'x [0] + = 1' en el ámbito interno. En Python3, podría mantener su código tal como está y usar la [palabra clave no local] (http://stackoverflow.com/a/1261952/190597). – unutbu

+0

"Mientras que la función interna puede leer las variables de la función externa, no puede escribirlas". - Esto es incorrecto según el comentario de unutbu. El problema es que cuando Python encuentra algo como x = ..., x se interpreta como una variable local, que por supuesto aún no está definida en ese punto. OTOH, si x es un objeto mutable con un método mutable, se puede modificar muy bien, p. si x es un objeto que admite el método inc() que se muta a sí mismo, x.inc() funcionará sin problemas. –

+0

@ThanhDK ¿No significa eso que no puede escribir en la variable? Cuando utilizas llamar a un método desde un objeto mutable, solo le estás diciendo que se modifique a sí mismo, * no estás * modificando realmente la variable (que simplemente contiene una referencia al objeto). En otras palabras, la referencia a la que apunta la variable 'x' permanece exactamente igual incluso si usted llama a' inc() 'o lo que sea, y usted no escribió efectivamente en la variable. – user193130

8

que tenía una situación en la que necesitaba un espacio de nombres separado, pero persistente. Utilicé clases. No lo hago de otra manera. 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 
+0

+1 por originalidad. –

6

Python 2 no tienen cierres - tenía soluciones que se parecían a cierres.

Hay un montón de ejemplos de respuestas ya dada - copia de las variables a la función interna, la modificación de un objeto en la función interna, etc.

En Python 3, el apoyo es más explícito - y sucinta:

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 une la función interna a la variable externa mencionado explícitamente, en ef fect que lo encierra. De ahí más explícitamente un 'cierre'.

0

Me gustaría ofrecer otra comparación simple entre Python y el ejemplo de 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 ejecución:

a = make(); g = a[0]; s = a[1]; 
s(2); g(); // 2 
s(3); g(); // 3 

Python:

def make(): 
    cl = 1 
    def gett(): 
    print(cl); 
    def sett (val): 
    cl = val 
    return gett, sett 

y ejecución:

g, s = make() 
g() #1 
s(2); g() #1 
s(3); g() #1 

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 ocurre lo mismo con JS, a menos que declare explícitamente uno con la palabra clave var.

Cuestiones relacionadas