Хорошо, потерпите меня, я знаю, что это будет выглядеть ужасно запутанным, но, пожалуйста, помогите мне понять, что происходит.
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
«упаковано» в локальную область видимости вложенной функции? Если нет, то как вызов вложенной функции ищет локальные переменные?
Я знаю, что столкновение с подобными проблемами обычно означает, что кто-то «делает это неправильно», но я хотел бы понять, что происходит.
for animal in ['cat', 'dog', 'cow']
... Я уверен, что кто-нибудь придет и объяснит это - это одна из тех проблем с Python :)Ответы:
Вложенная функция ищет переменные из родительской области при выполнении, а не при определении.
Тело функции компилируется, и «свободные» переменные (не определенные в самой функции путем присваивания) проверяются, затем привязываются к функции в качестве закрывающих ячеек, причем код использует индекс для ссылки на каждую ячейку.
pet_function
таким образом, есть одна свободная переменная (cage
), на которую затем ссылаются через закрывающую ячейку, индекс 0. Само замыкание указывает на локальную переменнуюcage
вget_petters
функции.Когда вы фактически вызываете функцию, это закрытие используется для просмотра значения
cage
в окружающей области видимости в момент вызова функции . Вот в чем проблема. К тому времени, когда вы вызываете свои функции,get_petters
функция уже завершила вычисление своих результатов.cage
Локальная переменная в какой - то момент во время этого исполнения был назначен каждому из'cow'
,'dog'
и'cat'
строк, но в конце функции,cage
содержит это последнее значение'cat'
. Таким образом, когда вы вызываете каждую из динамически возвращаемых функций, вы получаете'cat'
напечатанное значение .Обходной путь - не полагаться на закрытие. Вместо этого вы можете использовать частичную функцию , создать новую область действия или привязать переменную как значение по умолчанию для параметра ключевого слова .
Пример частичной функции с использованием
functools.partial()
:Создание нового примера области:
Привязка переменной как значения по умолчанию для параметра ключевого слова:
Нет необходимости определять
scoped_cage
функцию в цикле, компиляция выполняется только один раз, а не на каждой итерации цикла.источник
Я понимаю, что клетка ищется в пространстве имен родительской функции, когда фактически вызывается полученная функция pet_function, а не раньше.
Итак, когда вы это сделаете
Вы генерируете 3 функции, которые найдут последнюю созданную клетку.
Если вы замените последний цикл на:
Фактически вы получите:
источник
Это проистекает из следующих
после итерации значение
i
лениво сохраняется как окончательное значение.Как генератор функция будет работать (т.е. печатать каждое значение по очереди), но при преобразовании в список она запускается через генератор , поэтому все вызовы
cage
(cage.animal
) возвращают котов.источник
Упростим вопрос. Определите:
Тогда, как и в вопросе, получаем:
Но если мы избегаем создания
list()
первого:В чем дело? Почему эта тонкая разница полностью меняет наши результаты?
Если мы посмотрим
list(get_petters())
, то по изменению адресов памяти станет ясно, что мы действительно передаем три разные функции:Однако взгляните на
cell
s, с которыми связаны эти функции:Для обоих циклов
cell
объект остается неизменным на всех итерациях. Однако, как и ожидалось, конкретное, на чтоstr
он ссылается, меняется во втором цикле. Ссылается наcell
объектanimal
, который создается приget_petters()
вызове. Однако при запуске функции генератораanimal
изменяется, к какомуstr
объекту он относится .В первом цикле во время каждой итерации мы создаем все
f
s, но вызываем их только после того, как генераторget_petters()
полностью исчерпан иlist
уже создан a of functions.Во втором цикле во время каждой итерации мы приостанавливаем
get_petters()
генератор и вызываем егоf
после каждой паузы. Таким образом, мы получаем значениеanimal
в тот момент времени, когда функция генератора приостановлена.Как @Claudiu задает ответ на аналогичный вопрос :
источник