Variables locales en funciones anidadas en Python


Vale, ten paciencia conmigo en esto, sé que va a parecer horriblemente complicado, pero por favor ayúdame a entender lo que está pasando.

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 tengo tres animales diferentes? ¿No está el cage 'empaquetado' 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 entiende lo que pasa.

Author: Martijn Pieters, 2012-09-14

3 answers

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

El cuerpo de la función se compila, y las variables 'libres' (no definidas en la propia función 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 hace 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 realmente llamas a la función, ese cierre se usa para ver el valor de cage en el ámbito circundante en el momento en que llamas a la función. Aquí está el problema. En el momento en que llame a sus funciones, la función get_petters ya ha terminado de calcular sus resultados. La variable local cage en algún momento durante esa ejecución fue asignada a cada uno de los 'cow', 'dog', y 'cat' cadenas, pero al final de la función, cage contiene que ú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 cierres. Puede usar una función parcial en su lugar, crear un nuevo ámbito de función , o enlazar la variable como un valor predeterminado para un parámetro de palabra clave.

  • Ejemplo de función parcial, usando 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 ámbito ejemplo:

    def scoped_cage(cage=None):
        def pet_function():
            print "Mary pets the " + cage.animal + "."
        return pet_function
    
    yield (animal, partial(gotimes, scoped_cage(cage)))
    
  • Enlazar 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 hay necesidad de definir la función scoped_cage en el bucle, la compilación solo se lleva a cabo una vez, no en cada iteración del bucle.

 98
Author: Martijn Pieters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-29 13:27:37

Entiendo que cage se busca en el espacio de nombres de la función padre cuando se llama realmente a pet_function cedida, no antes.

Así que cuando lo hagas

funs = list(get_petters())

Genera 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ás:

cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.
 11
Author: Nicolas Barbey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-14 11:39:16

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) devuelve los gatos.

 5
Author: Andy Hayden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-05 17:06:51