¿Cómo funcionan los cierres léxicos? [duplicar]

Resuelto Eli Bendersky asked hace 16 años • 10 respuestas

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 iglobal. 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, ise desconoce en ese momento. Aún así, imprime "4 4 4".

Eli Bendersky avatar Oct 24 '08 21:10 Eli Bendersky
Aceptado

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.

Claudiu avatar Oct 24 '2008 14:10 Claudiu

Las funciones definidas en el bucle siguen accediendo a la misma variable imientras 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 iy utilizar su valor, un patrón común es establecerlo como parámetro predeterminado: los parámetros predeterminados se evalúan cuando defse 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)
piro avatar Oct 25 '2008 01:10 piro