¿Cómo funcionan los cierres léxicos? [duplicar]
Mientras investigaba un problema que tenía con cierres léxicos en código Javascript, encontré este problema en Python:
flist = []
for i in xrange(3):
def func(x): return x * i
flist.append(func)
for f in flist:
print f(2)
Tenga en cuenta que este ejemplo evita conscientemente lambda
. Imprime "4 4 4", lo cual es sorprendente. Esperaría "0 2 4".
Este código Perl equivalente lo hace bien:
my @flist = ();
foreach my $i (0 .. 2)
{
push(@flist, sub {$i * $_[0]});
}
foreach my $f (@flist)
{
print $f->(2), "\n";
}
Se imprime "0 2 4".
¿Puedes explicar la diferencia?
Actualizar:
El problema no es ser i
global. Esto muestra el mismo comportamiento:
flist = []
def outer():
for i in xrange(3):
def inner(x): return x * i
flist.append(inner)
outer()
#~ print i # commented because it causes an error
for f in flist:
print f(2)
Como muestra la línea comentada, i
se desconoce en ese momento. Aún así, imprime "4 4 4".
En realidad, Python se comporta según lo definido. Se crean tres funciones separadas , pero cada una tiene el cierre del entorno en el que están definidas ; en este caso, el entorno global (o el entorno de la función externa si el bucle se coloca dentro de otra función). Sin embargo, este es exactamente el problema: en este entorno, i se modifica y todos los cierres se refieren al mismo i .
Esta es la mejor solución que se me ocurre: crear un creador de funciones e invocarlo en su lugar. Esto obligará a diferentes entornos para cada una de las funciones creadas, con una i diferente en cada una.
flist = []
for i in xrange(3):
def funcC(j):
def func(x): return x * j
return func
flist.append(funcC(i))
for f in flist:
print f(2)
Esto es lo que sucede cuando se mezclan efectos secundarios y programación funcional.
Las funciones definidas en el bucle siguen accediendo a la misma variable i
mientras su valor cambia. Al final del ciclo, todas las funciones apuntan a la misma variable, que contiene el último valor del ciclo: el efecto es el que se muestra en el ejemplo.
Para evaluar i
y utilizar su valor, un patrón común es establecerlo como parámetro predeterminado: los parámetros predeterminados se evalúan cuando def
se ejecuta la instrucción y, por lo tanto, el valor de la variable del bucle se congela.
Lo siguiente funciona como se esperaba:
flist = []
for i in xrange(3):
def func(x, i=i): # the *value* of i is copied in func() environment
return x * i
flist.append(func)
for f in flist:
print f(2)