Variables locales en funciones anidadas

De acuerdo, ten esto en cuenta, sé que se verá horriblemente enrevesado, pero por favor, ayúdame a entender lo que está sucediendo.

from functools import partial class Cage(object): def __init__(self, animal): self.animal = animal def gotimes(do_the_petting): do_the_petting() def get_petters(): for animal in ['cow', 'dog', 'cat']: cage = Cage(animal) def pet_function(): print "Mary pets the " + cage.animal + "." yield (animal, partial(gotimes, pet_function)) funs = list(get_petters()) for name, f in funs: print name + ":", f() 

Da:

 cow: Mary pets the cat. dog: Mary pets the cat. cat: Mary pets the cat. 

Así que básicamente, ¿por qué no estoy recibiendo tres animales diferentes? ¿No está la cage ’empaquetada’ en el ámbito local de la función anidada? Si no, ¿cómo hace una llamada a la función anidada para buscar las variables locales?

Sé que encontrar este tipo de problemas por lo general significa que uno está “haciéndolo mal”, pero me gustaría entender qué sucede.

La función anidada busca variables del ámbito principal cuando se ejecuta, no cuando se define.

Se comstack el cuerpo de la función y se verifican las variables “libres” (no definidas en la propia función por asignación), luego se vinculan como celdas de cierre a la función, y el código utiliza un índice para hacer referencia a cada celda. pet_function tiene una variable libre ( cage ) a la que luego se hace referencia a través de una celda de cierre, el índice 0. El cierre en sí mismo apunta a la cage de la variable local en la función get_petters .

Cuando realmente llama a la función, ese cierre se usa para observar el valor de la cage en el ámbito circundante en el momento en que llama a la función . Aquí está el problema. Cuando usted llama a sus funciones, la función get_petters ya está lista para calcular sus resultados. La variable local de la cage en algún momento durante esa ejecución se asignó a cada una de las cadenas 'cow' , 'dog' y 'cat' , pero al final de la función, la cage contiene el último valor 'cat' . Por lo tanto, cuando llama a cada una de las funciones devueltas dinámicamente, obtiene el valor 'cat' impreso.

La solución es no confiar en los cierres. En su lugar, puede usar una función parcial , crear un nuevo ámbito de función o vincular la variable como valor predeterminado para un parámetro de palabra clave .

  • Ejemplo de función parcial, utilizando functools.partial() :

     from functools import partial def pet_function(cage=None): print "Mary pets the " + cage.animal + "." yield (animal, partial(gotimes, partial(pet_function, cage=cage))) 
  • Creando un nuevo ejemplo de scope:

     def scoped_cage(cage=None): def pet_function(): print "Mary pets the " + cage.animal + "." return pet_function yield (animal, partial(gotimes, scoped_cage(cage))) 
  • Enlace de la variable como valor predeterminado para un parámetro de palabra clave:

     def pet_function(cage=cage): print "Mary pets the " + cage.animal + "." yield (animal, partial(gotimes, pet_function)) 

No es necesario definir la función scoped_cage en el bucle, la comstackción solo se realiza una vez, no en cada iteración del bucle.

Según tengo entendido, la jaula se busca en el espacio de nombres de la función principal cuando en realidad se llama a pet_function, no antes.

Así que cuando lo hagas

 funs = list(get_petters()) 

Generarás 3 funciones que encontrarán la última jaula creada.

Si reemplaza su último bucle con:

 for name, f in get_petters(): print name + ":", f() 

En realidad obtendrá:

 cow: Mary pets the cow. dog: Mary pets the dog. cat: Mary pets the cat. 

Esto se deriva de lo siguiente

 for i in range(2): pass print i is 1 

después de iterar el valor de i se almacena perezosamente como su valor final.

Como generador, la función funcionaría (es decir, imprimiendo cada valor a su vez), pero cuando se transforma en una lista, se ejecuta sobre el generador , por lo tanto, todas las llamadas a cage ( cage.animal ) devuelven gatos.