LOADING

読み込みが遅い場合はキャッシュを有効にしてください。ブラウザはデフォルトで有効になっています

遅延バインディング (late binding)

目次


問題のコード

コード例1

まず、問題のコードを見てみましょう。

>>> my_ld = [lambda x: x * i for i in range(3)]
>>> my_list = [ld(2) for ld in my_ld]
>>> my_list
[4, 4, 4]
>>>

コード例2

>>> def my_func(x, a=1):
...     return x * a
... 
>>> my_func(1,3)
3
>>> my_func(5,3)
15
>>> my_ld = [lambda x: my_func(x, i) for i in range(3)]
>>> my_list = [ld(2) for ld in my_ld]
>>> my_list
[4, 4, 4]
  • lambda x: x * i:つまり、$x^i$という意味。

  • ld(2):つまり、$2 \times i$という意味、i[0,1,2]です。

  • 見込む結果は[0, 2, 4]ですが、実際の結果は[4, 4, 4]でした。

このコードでは、リスト内包表記の中で lambda x: x * i を定義していますが、各 lambda は変数 [i] の「現在の値」ではなく、「参照(アドレス)」を保持しています。

ループが終了した時点で [i] は 2 になっているため、すべての lambda 関数が x * 2 を実行することになり、結果として [4, 4, 4] になります。

解決方法1:デフォルト引数を使う

Python の関数や lambdaデフォルト引数は定義された時点で評価されるため、これを使って「現在の値を固定」することができます。

コード例1

>>> my_ld = [lambda x, a=i: x * a for i in range(3)]
>>> my_list = [ld(2) for ld in my_ld]
>>> my_list
[0, 2, 4]

コード例2

>>> my_ld = [lambda x, a=i: my_func(x, a) for i in range(3)]
>>> my_list = [ld(2) for ld in my_ld]
>>> my_list
[0, 2, 4]
  • a=i というデフォルト引数により、ループごとに i の値が固定されます。
  • lambda はそれぞれ a=0, a=1, a=2 を持つようになるので、期待通りの結果を得られます。

解決方法2:paritial を使う

functools.partial は Python 標準ライブラリの functools モジュールに含まれる関数で、関数の一部の引数を固定して新しい関数を作成するための機能です。

コード例1

>>> my_ld = [partial(lambda x, a: x * a, a=i) for i in range(3)]
>>> my_list = [ld(2) for ld in my_ld]
>>> my_list
[0, 2, 4]

コード例2

>>> my_ld = [partial(my_func, a=i) for i in range(3)]
>>> my_list = [ld(2) for ld in my_ld]
>>> my_list

結論

  • 遅延バインディングは Python 特有の挙動であり、特にループ内でクロージャや lambda を生成するときに注意が必要です。
  • デフォルト引数functools.partial を使い、意図した値を確実にキャプチャしましょう。
  • 可読性・保守性を考えると、functools.partial の使用も検討してください。
avatar
lijunjie2232
個人技術ブログ
My Github
目次0