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_function
por 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 localcage
en laget_petters
función.Cuando realmente llama a la función, ese cierre se usa para ver el valor de
cage
en el ámbito circundante en el momento en que llama a la función . Aquí radica el problema. Cuando llame a sus funciones, laget_petters
función ya habrá terminado de calcular sus resultados. Lacage
variable 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,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 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_cage
funció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
i
se 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
cell
s a los que están vinculadas estas funciones:Para ambos bucles, el
cell
objeto permanece igual a lo largo de las iteraciones. Sin embargo, como era de esperar, el específico alstr
que hace referencia varía en el segundo ciclo. Elcell
objeto alanimal
que se refiere , que se crea cuandoget_petters()
se llama. Sin embargo,animal
cambia elstr
objeto al que se refiere mientras se ejecuta la función del generador .En el primer ciclo, durante cada iteración, creamos todos los
f
s, pero solo los llamamos después de que el generadorget_petters()
esté completamente agotado y unlist
ya se ha creado función.En el segundo ciclo, durante cada iteración, estamos pausando el
get_petters()
generador y llamandof
después de cada pausa. Así, terminamos recuperando el valor deanimal
en ese momento en el que la función del generador está en pausa.Como @Claudiu responde a una pregunta similar :
fuente