質問を単純化しましょう。定義:
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())
、メモリアドレスの変更から、実際に3つの異なる関数が生成されることが明らかです。
>>> 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
参照する具体的な内容は2番目のループで異なります。cell
オブジェクトは、が呼び出さanimal
れたときに作成されるを参照します。get_petters()
ただし、ジェネレーター関数の実行時に参照するオブジェクトをanimal
変更します。str
最初のループでは、各反復中にすべてのsを作成しますが、ジェネレーターが完全に使い果たされ、関数のaが既に作成されf
た後でのみそれらを呼び出します。get_petters()
list
2番目のループでは、各反復中にget_petters()
ジェネレーターを一時停止し、一時停止するf
たびに呼び出します。animal
したがって、ジェネレーター関数が一時停止された時点の値を取得することになります。
@Claudiuが同様の質問に答えているように:
3つの別個の関数が作成されますが、それぞれが定義された環境(この場合はグローバル環境(またはループが別の関数内に配置されている場合は外部関数の環境))のクロージャーを持ちます。これはまさに問題ですが、この環境でanimal
は変更されており、クロージャはすべて同じを参照していanimal
ます。
[編集者注:i
に変更されましたanimal
。]