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