37419


Python3において以下の二つのコードは異なる挙動をします：

[code]

    fs = [lambda x: i*x for i in range(3)]
    for i in range(3):
        print(fs[i](3))
    
[/code]

これは`6 6 6`と出力され、

[code]

    fs2 = []
    for i in range(3):
        fs2.append(lambda x: i*x)
    for i in range(3):
        print(fs2[i](3))
    
[/code]

こちらは`0 3 6`と出力されます。  
両者は一見同じ挙動をするように思えますが、どうして異なった挙動をするのでしょうか？


CC BY-SA 3.0
2017-08-22T17:05:36.860
37416
2017-08-29T12:53:50.807
2017-08-29T12:53:50.807
13199
9572
question
python python3
リスト内包表記中のラムダ式が変数をキャプチャする際の挙動


Pythonでは、lambdaや関数で外部の値を参照したときに、変数の束縛（バインド）が行われます。

今回起こっていることをおおざっぱに解説すると、 `fs = [lambda x: i*x for i in range(3)]`
ではループ毎に実値(0,1,2)を束縛するのではなく、変数(i)そのものを束縛しています。このため、束縛した変数 `i`
はループごとに値が変わってしまい、最後の値(i=2)が束縛された状態で終了します。

結果として、後で使う時、 `i*x` の束縛変数`i`は常に2なので、`2*x` => `2*3=6` となります。

* * *

8/24追記

> この説明だと最初の6 6 6の出力は理解できますが、後者の挙動が理解できません。 – termoshtt

コメントを頂いたので、後者のコードについても説明してみます。

"実値(0,1,2)を束縛するのではなく、変数(i)そのものを束縛しています" と前に説明しましたが、この挙動は後者のコードでも発生しています。

1つ目のforループで変数iを使っていて、2つ目のforループも変数iを使っています。このため、lambdaに束縛されている変数 `i`
は、1つ目のforループのiの最終値(i=2)ではなく、2つ目のforループで使われているiの値(0,1,2)を参照します。

このため、 `fs2[i](3)` は `(lambda x: x*i)(3)` ですが、iは0,1,2と変化していきます。結果として、0*3=0,
1*3=3, 2*3=6 という値がプリントされます。

そこで、2つ目のforループの変数名を `i` ではなく `j` にするとどうなるでしょうか。

[code]

    >>> fs2 = []
    >>> for i in range(3):
    ...     fs2.append(lambda x: i*x)
    ... 
    >>> for i in range(3):
    ...     print(fs2[i](3))
    ... 
    0
    3
    6
    >>> for j in range(3):
    ...     print(fs2[j](3))
    ... 
    6
    6
    6
    
[/code]

i=2で固定されたため、プリントされる値も常に `2*3=6` となりました。

* * *

詳しい解説については以下のページが参考になります。

  * [クロージャのひみつ - atsuoishimoto's diary](http://atsuoishimoto.hatenablog.com/entry/20101006/1286338675)
  * [func_closureのひみつ - atsuoishimoto's diary](http://atsuoishimoto.hatenablog.com/entry/20110402/1301709054)


CC BY-SA 3.0
2017-08-22T23:23:03.680
37419
2017-08-25T22:16:19.330
2017-08-25T22:16:19.330
806
806
37416
answer