Давайте спростимо питання. Визначте:
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функція вже створена.
У другому циклі під час кожної ітерації ми робимо паузу на get_petters()генераторі та викликаємо fпісля кожної паузи. Таким чином, ми в кінцевому підсумку отримуємо значення animalв той момент часу, коли функція генератора призупинена.
Як @Claudiu ставить відповідь на подібне запитання :
Три окремі функції створюються, але кожна з них має закриття середовища, яке вони визначають, - у цьому випадку глобальне середовище (або середовище зовнішньої функції, якщо цикл розміщений всередині іншої функції). Саме в цьому і полягає проблема - в цьому середовищі animalмутують, і всі закриття стосуються одного і того ж animal.
[Примітка редактора: iзмінено на animal.]
for animal in ['cat', 'dog', 'cow']... Я впевнений, що хтось підійде і пояснить це, хоча - це один із тих пітхонів :)