2012-09-14 21 views
86

Bien, tengan paciencia conmigo en esto, sé que se verá terriblemente complicado, pero ayúdenme a comprender lo que está sucediendo.Variables locales en las funciones anidadas de Python

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 recibo tres animales diferentes? ¿No está 'empaquetado' el cage en el alcance 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 'lo está haciendo mal', pero me gustaría entender qué sucede.

+1

Try 'por animal en [ 'gato', 'perro', 'vaca'] '. Sin embargo, estoy seguro de que alguien vendrá y me explicará esto, es uno de esos de Python gotcha :) –

Respuesta

93

La función anidada busca 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 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 local cage en la función get_petters.

Cuando en realidad se llama a la función, que el cierre se utiliza entonces para mirar el valor de cage en el perímetro que rodea en el momento de llamar 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 consiste en no confiar en los cierres. Puede usar una función parcial en su lugar, crear una nueva función ámbito, o vincular la variable como un valor predeterminado para un parámetro de palabra clave.

  • ejemplo 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))) 
    
  • Creación de un nuevo ejemplo ámbito de aplicación:

    def scoped_cage(cage=None): 
        def pet_function(): 
         print "Mary pets the " + cage.animal + "." 
        return pet_function 
    
    yield (animal, partial(gotimes, scoped_cage(cage))) 
    
  • Encuadernación 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 bucle, la compilación solo tiene lugar una vez, no en cada iteración del ciclo.

+0

Me golpeé la cabeza en esta pared durante 3 horas hoy en un guión de trabajo. Su último punto es muy importante, y es la razón principal por la que encontré este problema. Tengo devoluciones de llamadas con cierres en abundancia a lo largo de mi código, pero probar la misma técnica en un bucle es lo que me atrapó. – DrEsperanto

5

Esto se deriva de la siguiente

for i in range(2): 
    pass 

print i is 1 

después de la iteración el valor de i se almacena perezosamente como su valor final.

Como generador de la función sería trabajar (es decir, la impresión de cada valor a su vez), pero al transformar a una lista que se ejecuta sobre el generador de, por lo tanto, todas las llamadas a cage (cage.animal) volver gatos.

11

Mi entendimiento es que la jaula se busca en el espacio de nombres de la función padre cuando se llama realmente la función pet_definada, no antes.

Así que cuando se hacen

funs = list(get_petters()) 

Usted genera 3 funciones que encontrarán la jaula por último creado.

Si reemplaza el último bucle con:

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

Que se va a obtener:

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