プログラミング言語Pythonにおいて、関数やラムダ式にローカル変数の「値」を束縛する方法。
下記コードでは “引数x
を定義時の値n
で冪乗” する関数を個別生成するつもりが、実際には “引数x
を変数n
の値で冪乗” する同一の関数が生成される。forループ終了後の変数n
は値3
となっており、プログラマの期待に反する実行結果となる。
# 関数 ops = [] for n in range(4): def fn(x): return x ** n ops.append(fn) print([f(2) for f in ops]) # [8, 8, 8, 8] # ラムダ式 ops = [] for n in range(4): ops.append(lambda x: x ** n) print([f(2) for f in ops]) # [8, 8, 8, 8]
関数やラムダ式のデフォルト引数値は定義時に評価される*1ことを利用し、次のようにローカル変数の値を束縛できる。
# 関数 ops = [] for n in range(4): def fn(x, n=n): return x ** n ops.append(fn) print([f(2) for f in ops]) # [1, 2, 4, 8] # ラムダ式 ops = [] for n in range(4): ops.append(lambda x, n=n: x ** n) print([f(2) for f in ops]) # [1, 2, 4, 8]
他の実装手段として、関数/ラムダ式を入れ子にし外側だけ評価する方法や、 functools.partial 関数を利用する方法がある。
# 関数の入れ子 ops = [] for n in range(4): def fn(n): def fn0(x): return x ** n return fn0 ops.append(fn(n)) print([f(2) for f in ops]) # [1, 2, 4, 8] # ラムダ式の入れ子と即時評価 ops = [] for n in range(4): fn = (lambda n: lambda x: x ** n)(n) ops.append(fn) print([f(2) for f in ops]) # [1, 2, 4, 8]
from functools import partial # 関数+partial ops = [] for n in range(4): def fn(x, n): return x ** n ops.append(partial(fn, n=n)) print([f(2) for f in ops]) # [1, 2, 4, 8] # ラムダ式+partial ops = [] for n in range(4): fn = lambda x, n: x ** n ops.append(partial(fn, n=n)) print([f(2) for f in ops]) # [1, 2, 4, 8]
関連URL