簡體   English   中英

在循環(或理解)中創建函數(或 lambda)

[英]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的早期綁定

    與閉包變量(如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.partiali的當前值綁定到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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM