procedimientos - variables globales python 3
Variables locales en funciones anidadas de Python (3)
Esto proviene 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 por turno), pero cuando se transforma en una lista se ejecuta sobre el generador , por lo tanto, todas las llamadas a cage
( cage.animal
) devuelven gatos.
De acuerdo, tengan paciencia conmigo en esto, sé que se verá terriblemente complicado, pero ayúdenme a comprender 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.
Entonces, básicamente, ¿por qué no recibo tres animales diferentes? ¿La cage
no está "empaquetada" en el ámbito local de la función anidada? Si no, ¿cómo una llamada a la función anidada busca las variables locales?
Sé que encontrarse con este tipo de problemas generalmente significa que uno está "haciéndolo mal", pero me gustaría entender qué sucede.
La función anidada busca las variables del ámbito principal cuando se ejecuta, no cuando está definido.
El cuerpo de la función se compila y las variables ''libres'' (no definidas en la función por asignación) se verifican y luego se unen como celdas de cierre a la función, con el código usando un índice para hacer referencia a cada celda. pet_function
tiene así una variable libre ( cage
) que luego se referencia a través de una celda de cierre, índice 0. El cierre en sí 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 ver el valor de la cage
en el ámbito circundante en el momento en que llama a la función . Aquí yace el problema. Para cuando llame a sus funciones, la función get_petters
ya está lista para calcular sus resultados. La variable local 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, cage
contiene ese ú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 cierres. En su lugar, puede usar una función parcial , crear un nuevo alcance de función o vincular la variable como un 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 alcance:
def scoped_cage(cage=None): def pet_function(): print "Mary pets the " + cage.animal + "." return pet_function yield (animal, partial(gotimes, scoped_cage(cage)))
Vinculando la variable como un 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 ciclo, la compilación solo tiene lugar una vez, no en cada iteración del ciclo.
Según entiendo, la jaula se busca en el espacio de nombres de la función padre cuando se llama realmente la función pet_final, no antes.
Entonces cuando lo haces
funs = list(get_petters())
Generas 3 funciones que encontrarán la jaula creada por última vez.
Si reemplazas tu último ciclo con:
for name, f in get_petters():
print name + ":",
f()
En realidad obtendrás:
cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.