繁体   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