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