Упростим вопрос. Определите:
def get_petters():
for animal in ['cow', 'dog', 'cat']:
def pet_function():
return "Mary pets the " + animal + "."
yield (animal, pet_function)
Тогда, как и в вопросе, получаем:
>>> for name, f in list(get_petters()):
... print(name + ":", f())
cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.
Но если мы избегаем создания list()первого:
>>> for name, f in get_petters():
... print(name + ":", f())
cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.
В чем дело? Почему эта тонкая разница полностью меняет наши результаты?
Если мы посмотрим list(get_petters()), то по изменению адресов памяти станет ясно, что мы действительно передаем три разные функции:
>>> list(get_petters())
[('cow', <function get_petters.<locals>.pet_function at 0x7ff2b988d790>),
('dog', <function get_petters.<locals>.pet_function at 0x7ff2c18f51f0>),
('cat', <function get_petters.<locals>.pet_function at 0x7ff2c14a9f70>)]
Однако взгляните на cells, с которыми связаны эти функции:
>>> for _, f in list(get_petters()):
... print(f(), f.__closure__)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
Mary pets the cat. (<cell at 0x7ff2c112a9d0: str object at 0x7ff2c3f437f0>,)
>>> for _, f in get_petters():
... print(f(), f.__closure__)
Mary pets the cow. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a95670>,)
Mary pets the dog. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c1a952f0>,)
Mary pets the cat. (<cell at 0x7ff2b86b5d00: str object at 0x7ff2c3f437f0>,)
Для обоих циклов cellобъект остается неизменным на всех итерациях. Однако, как и ожидалось, конкретное, на что strон ссылается, меняется во втором цикле. Ссылается на cellобъект animal, который создается при get_petters()вызове. Однако при запуске функции генератораanimal изменяется, к какому strобъекту он относится .
В первом цикле во время каждой итерации мы создаем все fs, но вызываем их только после того, как генератор get_petters()полностью исчерпан и listуже создан a of functions.
Во втором цикле во время каждой итерации мы приостанавливаем get_petters()генератор и вызываем его fпосле каждой паузы. Таким образом, мы получаем значение animalв тот момент времени, когда функция генератора приостановлена.
Как @Claudiu задает ответ на аналогичный вопрос :
Создаются три отдельные функции, но каждая из них закрывает среду, в которой они определены, - в данном случае глобальную среду (или среду внешней функции, если цикл помещен внутри другой функции). Но проблема именно в этом - в этой среде animalпроисходит мутация, и все замыкания относятся к одному и тому же animal.
[Примечание редактора: iбыло изменено на animal.]
for animal in ['cat', 'dog', 'cow']... Я уверен, что кто-нибудь придет и объяснит это - это одна из тех проблем с Python :)