説明
ここでの問題はi
、関数のf
作成時にの値が保存されないことです。むしろ、それが呼び出されたときf
の値を調べます。i
考えてみれば、この動作は完全に理にかなっています。実際、これは関数が機能するための唯一の合理的な方法です。次のように、グローバル変数にアクセスする関数があるとします。
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
このコードを読んだとき、当然のことながら、「foo」ではなく「bar」が出力されることを期待global_var
します。関数の宣言後にの値が変更されたためです。同じことがあなた自身のコードでも起こっています:あなたがを呼び出すf
までに、の値はi
に変更され、に設定されてい2
ます。
ソリューション
この問題を解決するには多くの方法があります。ここにいくつかのオプションがあります:
i
デフォルト引数として使用することにより、事前バインディングを強制します
(のようなi
)クロージャー変数とは異なり、デフォルトの引数は、関数が定義されるとすぐに評価されます。
for i in range(3):
def f(i=i): # <- right here is the important bit
return i
functions.append(f)
これがどのように/なぜ機能するかについて少し洞察を与えるために:関数のデフォルト引数は関数の属性として保存されます。したがって、の現在の値i
がスナップショットで保存されます。
>>> i = 0
>>> def f(i=i):
... pass
>>> f.__defaults__ # this is where the current value of i is stored
(0,)
>>> # assigning a new value to i has no effect on the function's default arguments
>>> i = 5
>>> f.__defaults__
(0,)
関数ファクトリーを使用i
して、クロージャー内の現在の値を取得します。
あなたの問題の根本i
は、それが変化する可能性がある変数であるということです。変更されないことが保証されている別の変数を作成することで、この問題を回避できます。これを行う最も簡単な方法は、クロージャーです。
def f_factory(i):
def f():
return i # i is now a *local* variable of f_factory and can't ever change
return f
for i in range(3):
f = f_factory(i)
functions.append(f)
使用functools.partial
の現在の値バインドするi
にはf
functools.partial
既存の関数に引数をアタッチできます。ある意味では、それも一種の関数ファクトリーです。
import functools
def f(i):
return i
for i in range(3):
f_with_i = functools.partial(f, i) # important: use a different variable than "f"
functions.append(f_with_i)
警告:これらのソリューションは、変数に新しい値を割り当てる場合にのみ機能します。変数に格納されているオブジェクトを変更すると、同じ問題が再び発生します。
>>> i = [] # instead of an int, i is now a *mutable* object
>>> def f(i=i):
... print('i =', i)
...
>>> i.append(5) # instead of *assigning* a new value to i, we're *mutating* it
>>> f()
i = [5]
i
デフォルトの引数に変えたにもかかわらず、いかに変更されたかに注意してください!コードが変化 する場合は、次のようにのコピーを関数にi
バインドする必要がありますi
。
def f(i=i.copy()):
f = f_factory(i.copy())
f_with_i = functools.partial(f, i.copy())