yohhoyの日記

技術的メモをしていきたい日記

関数/ラムダ式への値束縛

プログラミング言語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