Локальные переменные во вложенных функциях 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()

выдает:

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

Итак, в принципе, почему я не получаю трех разных животных? Разве это не cage "упаковано" в локальную область вложенной функции? Если нет, то как вызов вложенной функции ищет локальные переменные?

Я знаю, что столкнувшись с такого рода проблемами, как правило, означает, что один "делаю это неправильно", но я хотел бы понять, что происходит.

3 93

3 ответа:

вложенная функция ищет переменные из родительской области при выполнении, а не при определении.

тело функции компилируется, и "свободные" переменные (не определенные в самой функции назначением) проверяются, а затем привязываются как ячейки закрытия к функции, причем код использует индекс для ссылки на каждую ячейку. и один свободной переменной (cage), на который затем ссылаются через ячейку закрытия, индекс 0. Само закрытие пунктов локальная переменная cage на создать новый объем функции, или привязать переменную как значение по умолчанию для параметра ключевое слово.

  • пример частичной функции, используя 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)))
    
  • создание нового примера объем:

    def scoped_cage(cage=None):
        def pet_function():
            print "Mary pets the " + cage.animal + "."
        return pet_function
    
    yield (animal, partial(gotimes, scoped_cage(cage)))
    
  • привязка переменной в качестве значения по умолчанию для параметра сайта:

    def pet_function(cage=cage):
        print "Mary pets the " + cage.animal + "."
    
    yield (animal, partial(gotimes, pet_function))
    

нет необходимости определять scoped_cage функция в цикле, компиляция происходит только один раз, а не на каждой итерации цикла.

Я понимаю, что клетка ищется в пространстве имен родительской функции, когда на самом деле вызывается данная функция pet_function, а не раньше.

Так что, когда вы делаете

funs = list(get_petters())

вы генерируете 3 функции, которые найдут наконец созданную клетку.

Если вы замените свой последний цикл на:

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

вы получаете :

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

это вытекает из следующего

for i in range(2): 
    pass

print i is 1

после прохода стоимостью i лениво хранится как его конечное значение.

в качестве генератора функция будет работать (т. е. печатать каждое значение по очереди), но при преобразовании в список он работает над генератором, отсюда все звонки в cage (cage.animal) вернуть кошек.