説明
ここでの問題は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())