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