Uprośćmy pytanie. Definiować:
def get_petters():
for animal in ['cow', 'dog', 'cat']:
def pet_function():
return "Mary pets the " + animal + "."
yield (animal, pet_function)
Następnie, tak jak w pytaniu, otrzymujemy:
>>> 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.
Ale jeśli unikniemy tworzenia list()
pierwszego:
>>> for name, f in get_petters():
... print(name + ":", f())
cow: Mary pets the cow.
dog: Mary pets the dog.
cat: Mary pets the cat.
Co się dzieje? Dlaczego ta subtelna różnica całkowicie zmienia nasze wyniki?
Jeśli się przyjrzymy list(get_petters())
, ze zmieniających się adresów pamięci jasno wynika, że rzeczywiście dajemy trzy różne funkcje:
>>> 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>)]
Jednak spójrz na elementy, cell
które te funkcje są powiązane:
>>> 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>,)
W przypadku obu pętli cell
obiekt pozostaje taki sam przez wszystkie iteracje. Jednak, zgodnie z oczekiwaniami, specyfika, do str
której się odnosi, różni się w drugiej pętli. cell
Obiektu odnosi się do animal
, który tworzy się, gdy get_petters()
jest tzw. Jednak animal
zmienia str
obiekt, do którego się odnosi, gdy działa funkcja generatora .
W pierwszej pętli podczas każdej iteracji tworzymy wszystkie f
s, ale wywołujemy je dopiero po get_petters()
całkowitym wyczerpaniu generatora i utworzeniu jednej list
z funkcji.
W drugiej pętli, podczas każdej iteracji, zatrzymujemy get_petters()
generator i dzwonimy f
po każdej przerwie. W ten sposób otrzymujemy wartość animal
w tym momencie, w którym funkcja generatora jest wstrzymana.
Jak @Claudiu odpowiada na podobne pytanie :
Tworzone są trzy oddzielne funkcje, ale każda z nich ma zamknięcie środowiska, w którym została zdefiniowana - w tym przypadku środowisko globalne (lub środowisko funkcji zewnętrznej, jeśli pętla jest umieszczona wewnątrz innej funkcji). W tym jednak dokładnie tkwi problem - w tym środowisku animal
jest zmutowany, a wszystkie zamknięcia odnoszą się do tego samego animal
.
[Uwaga redaktora: i
zmieniono na animal
.]
for animal in ['cat', 'dog', 'cow']
... Jestem pewien, że ktoś przyjdzie i to wyjaśni - to jeden z tych Pythona gotcha :)