[英]Creating functions (or lambdas) in a loop (or comprehension)
我正在嘗試在循環內創建函數:
functions = []
for i in range(3):
def f():
return i
# alternatively: f = lambda: i
functions.append(f)
問題是所有功能最終都是一樣的。 所有三個函數都返回 2,而不是返回 0、1 和 2:
print([f() for f in functions])
# expected output: [0, 1, 2]
# actual output: [2, 2, 2]
為什么會發生這種情況,我應該怎么做才能分別獲得 output 0、1 和 2 的 3 個不同的函數?
您遇到了后期綁定的問題——每個函數都盡可能晚地查找i
(因此,在循環結束后調用時, i
將設置為2
)。
通過強制早期綁定輕松修復:將def f():
更改為def f(i=i):
如下所示:
def f(i=i):
return i
默認值( i=i
中的右側i
是參數名稱i
的默認值,即i=i
中的左側i
)是在def
時間而不是在call
時查找的,所以基本上它們是一種專門尋找早期綁定的方法。
如果您擔心f
得到一個額外的參數(因此可能會被錯誤地調用),那么有一種更復雜的方法涉及使用閉包作為“函數工廠”:
def make_f(i):
def f():
return i
return f
並在您的循環中使用f = make_f(i)
而不是def
語句。
這里的問題是創建函數f
時沒有保存i
的值。 相反, f
在調用時查找i
的值。
如果你仔細想想,這種行為是完全合理的。 事實上,這是函數工作的唯一合理方式。 想象一下,您有一個訪問全局變量的函數,如下所示:
global_var = 'foo'
def my_function():
print(global_var)
global_var = 'bar'
my_function()
當您閱讀這段代碼時,您當然會期望它打印“bar”,而不是“foo”,因為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())
要添加到@Aran-Fey 的出色答案,在第二個解決方案中,您可能還希望修改函數內部的變量,這可以使用關鍵字nonlocal
來完成:
def f_factory(i):
def f(offset):
nonlocal i
i += offset
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)
print(f(10))
你可以這樣嘗試:
l=[]
for t in range(10):
def up(y):
print(y)
l.append(up)
l[5]('printing in 5th function')
您必須將每個i
值保存在 memory 中的單獨空間中,例如:
class StaticValue:
val = None
def __init__(self, value: int):
StaticValue.val = value
@staticmethod
def get_lambda():
return lambda x: x*StaticValue.val
class NotStaticValue:
def __init__(self, value: int):
self.val = value
def get_lambda(self):
return lambda x: x*self.val
if __name__ == '__main__':
def foo():
return [lambda x: x*i for i in range(4)]
def bar():
return [StaticValue(i).get_lambda() for i in range(4)]
def foo_repaired():
return [NotStaticValue(i).get_lambda() for i in range(4)]
print([x(2) for x in foo()])
print([x(2) for x in bar()])
print([x(2) for x in foo_repaired()])
Result:
[6, 6, 6, 6]
[6, 6, 6, 6]
[0, 2, 4, 6]
我目前正在為此開發一個模塊,但您想要做的是創建一個腳本,將函數寫入另一個 python 文件,如下所示
try:
FunctionFile = open(outputfolder + "/FunctionFile.", "x")
except:
print("\noutput FunctionFile file exists or error")
FunctionFile = open(outputfolder + "/" + filename + ".py", "a")
FunctionFile.write("import os...\n\n")
functions = [1,2,3,4,5]
for x in functions:
FunctionFile.write("\ndef " + x + '():\n')
FunctionFile.write(" whatever you want it to be"))
FunctionFile.write("\n\n")
這只會工作一次,一旦生成列表,您將需要開發一些代碼來檢查現有或已刪除的功能。
只需修改最后一行
functions.append(f())
編輯:這是因為f
是一個 function - python 將函數視為一等公民,您可以將它們傳遞給稍后調用的變量。 因此,您的原始代碼所做的是將 function 本身附加到列表中,而您想要做的是將 append 的結果添加到列表中,這是上面的行通過調用 function 實現的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.