2012-07-10 13 views
7

A continuación se muestra un ejemplo que obtuve del blog de alguien sobre el cierre de Python. Lo ejecuto en python 2.7 y obtengo una salida diferente de la que esperaba.Acerca del cierre de pitón

flist = [] 

for i in xrange(3): 
    def func(x): 
     return x*i 
    flist.append(func) 

for f in flist: 
    print f(2) 

Mi salida esperada es: 0, 2, 4
Pero la salida es: 4, 4, 4
¿Hay alguien podría ayudar a explicar por que?
Gracias de antemano.

+3

posible duplicado de [cierres léxicos en Python] (http://stackoverflow.com/questions/233673/lexical-closures-in-python) – BrenBarn

Respuesta

16

Loops no introducen alcance en Python, por lo que las tres funciones cierre durante el mismo i variable y se referirá a su valor final después de que finalice bucle, que es 2.

Parece que casi todo el mundo me hablar con quién usa cierres en Python ha sido mordido por esto. El corolario es que la función externa puede cambiar i pero la función interna no puede (ya que eso haría que i sea un local en lugar de un cierre basado en las reglas sintácticas de Python).

Hay dos maneras de abordar este:

# avoid closures and use default args which copy on function definition 
for i in xrange(3): 
    def func(x, i=i): 
     return x*i 
    flist.append(func) 

# or introduce an extra scope to close the value you want to keep around: 
for i in xrange(3): 
    def makefunc(i): 
     def func(x): 
      return x*i 
     return func 
    flist.append(makefunc(i)) 

# the second can be simplified to use a single makefunc(): 
def makefunc(i): 
    def func(x): 
     return x*i 
    return func 
for i in xrange(3): 
    flist.append(makefunc(i)) 

# if your inner function is simple enough, lambda works as well for either option: 
for i in xrange(3): 
    flist.append(lambda x, i=i: x*i) 

def makefunc(i): 
    return lambda x: x*i 
for i in xrange(3): 
    flist.append(makefunc(i)) 
+0

+1 Gran explicación y solución. – jamylak

+0

Nota para otros lectores: Python 3 agrega la palabra clave 'nonlocal', que permitiría a cada' func' cambiar el valor de 'i', que a su vez afectaría a los demás. No es útil en este caso, pero es potencialmente útil si tiene varias funciones internas. –

+0

Puede simplificar el último un poco más con lambda, aunque eso restringe lo que func() puede hacer. – Dubslow

4

No está creando cierres. Está generando una lista de funciones que cada una accede a la variable global i que es igual a 2 después del primer ciclo. Por lo tanto, terminas con 2 * 2 para cada llamada de función.

+0

Gracias por la responder. Si quiero crear un cierre para obtener mi salida esperada, ¿cómo puedo cambiar el código? ¿Podría darme alguna sugerencia? –

+0

@ Alex.Zhang: Bueno, pediste una explicación de por qué el comportamiento experimentado no es el mismo que esperabas. Consulte http://stackoverflow.com/a/11408601/21945 para obtener una solución. – mhawke

1

Cada función accede a lo global i.

functools.partial viene al rescate:

from functools import partial 
flist = [] 

for i in xrange(3): 
    def func(x, multiplier=None): 
     return x * multiplier 
    flist.append(partial(func, multiplier=i))