De acuerdo, tengan paciencia conmigo en esto, sé que se verá horriblemente complicado, pero por favor ayúdenme 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.
Entonces, básicamente, ¿por qué no obtengo tres animales diferentes? ¿No está el cage'empaquetado' en el ámbito local de la función anidada? Si no es así, ¿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 'lo está haciendo mal', pero me gustaría entender qué sucede.

for animal in ['cat', 'dog', 'cow']... Estoy seguro de que alguien vendrá y explicará esto, es uno de esos problemas de Python :)Respuestas:
La función anidada busca variables del ámbito principal cuando se ejecuta, no cuando se define.
El cuerpo de la función se compila y las variables 'libres' (no definidas en la función en sí por asignación), se verifican, 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_functionpor lo tanto, tiene 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 variable localcageen laget_pettersfunción.Cuando realmente llama a la función, ese cierre se usa para ver el valor de
cageen el ámbito circundante en el momento en que llama a la función . Aquí radica el problema. Cuando llame a sus funciones, laget_pettersfunción ya habrá terminado de calcular sus resultados. Lacagevariable local en algún momento de que la ejecución se asigna a cada uno de los'cow','dog'y'cat'cadenas, pero al final de la función,cagecontiene 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 depender de los cierres. En su lugar, puede utilizar 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, usando
functools.partial():Creación de un nuevo ejemplo de alcance:
Vincular la variable como valor predeterminado para un parámetro de palabra clave:
No es necesario definir la
scoped_cagefunción en el bucle, la compilación solo se realiza una vez, no en cada iteración del bucle.fuente
Tengo entendido que la jaula se busca en el espacio de nombres de la función principal cuando se llama realmente a la función pet_function producida, no antes.
Así que cuando lo hagas
Generas 3 funciones que encontrarán la última jaula creada.
Si reemplaza su último bucle con:
Realmente obtendrás:
fuente
Esto se deriva de lo siguiente
después de iterar el valor de
ise almacena perezosamente como su valor final.Como generador, la función funcionaría (es decir, imprimir 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.fuente
Simplifiquemos la pregunta. Definir:
Entonces, al igual que en la pregunta, obtenemos:
Pero si evitamos crear una
list()primera:¿Que esta pasando? ¿Por qué esta sutil diferencia cambia completamente nuestros resultados?
Si miramos
list(get_petters()), está claro por las direcciones de memoria cambiantes que de hecho producimos tres funciones diferentes:Sin embargo, eche un vistazo a los
cells a los que están vinculadas estas funciones:Para ambos bucles, el
cellobjeto permanece igual a lo largo de las iteraciones. Sin embargo, como era de esperar, el específico alstrque hace referencia varía en el segundo ciclo. Elcellobjeto alanimalque se refiere , que se crea cuandoget_petters()se llama. Sin embargo,animalcambia elstrobjeto al que se refiere mientras se ejecuta la función del generador .En el primer ciclo, durante cada iteración, creamos todos los
fs, pero solo los llamamos después de que el generadorget_petters()esté completamente agotado y unlistya se ha creado función.En el segundo ciclo, durante cada iteración, estamos pausando el
get_petters()generador y llamandofdespués de cada pausa. Así, terminamos recuperando el valor deanimalen ese momento en el que la función del generador está en pausa.Como @Claudiu responde a una pregunta similar :
fuente