简体   繁体   English

在循环(或理解)中创建函数(或 lambda)

[英]Creating functions (or lambdas) in a loop (or comprehension)

I'm trying to create functions inside of a loop:我正在尝试在循环内创建函数:

functions = []

for i in range(3):
    def f():
        return i

    # alternatively: f = lambda: i

    functions.append(f)

The problem is that all functions end up being the same.问题是所有功能最终都是一样的。 Instead of returning 0, 1, and 2, all three functions return 2:所有三个函数都返回 2,而不是返回 0、1 和 2:

print([f() for f in functions])
# expected output: [0, 1, 2]
# actual output:   [2, 2, 2]

Why is this happening, and what should I do to get 3 different functions that output 0, 1, and 2 respectively?为什么会发生这种情况,我应该怎么做才能分别获得 output 0、1 和 2 的 3 个不同的函数?

You're running into a problem with late binding -- each function looks up i as late as possible (thus, when called after the end of the loop, i will be set to 2 ).您遇到了后期绑定的问题——每个函数都尽可能晚地查找i (因此,在循环结束后调用时, i将设置为2 )。

Easily fixed by forcing early binding: change def f(): to def f(i=i): like this:通过强制早期绑定轻松修复:将def f():更改为def f(i=i):如下所示:

def f(i=i):
    return i

Default values (the right-hand i in i=i is a default value for argument name i , which is the left-hand i in i=i ) are looked up at def time, not at call time, so essentially they're a way to specifically looking for early binding.默认值( i=i中的右侧i是参数名称i的默认值,即i=i中的左侧i )是在def时间而不是在call时查找的,所以基本上它们是一种专门寻找早期绑定的方法。

If you're worried about f getting an extra argument (and thus potentially being called erroneously), there's a more sophisticated way which involved using a closure as a "function factory":如果您担心f得到一个额外的参数(因此可能会被错误地调用),那么有一种更复杂的方法涉及使用闭包作为“函数工厂”:

def make_f(i):
    def f():
        return i
    return f

and in your loop use f = make_f(i) instead of the def statement.并在您的循环中使用f = make_f(i)而不是def语句。

The Explanation说明

The issue here is that the value of i is not saved when the function f is created.这里的问题是创建函数f时没有保存i的值。 Rather, f looks up the value of i when it is called .相反, f调用时查找i的值。

If you think about it, this behavior makes perfect sense.如果你仔细想想,这种行为是完全合理的。 In fact, it's the only reasonable way functions can work.事实上,这是函数工作的唯一合理方式。 Imagine you have a function that accesses a global variable, like this:想象一下,您有一个访问全局变量的函数,如下所示:

global_var = 'foo'

def my_function():
    print(global_var)

global_var = 'bar'
my_function()

When you read this code, you would - of course - expect it to print "bar", not "foo", because the value of global_var has changed after the function was declared.当您阅读这段代码时,您当然会期望它打印“bar”,而不是“foo”,因为global_var的值在函数声明后发生了变化。 The same thing is happening in your own code: By the time you call f , the value of i has changed and been set to 2 .同样的事情也发生在您自己的代码中:当您调用f时, i的值已更改并设置为2

The Solution解决方案

There are actually many ways to solve this problem.实际上有很多方法可以解决这个问题。 Here are a few options:这里有几个选项:

  • Force early binding of i by using it as a default argument通过将 i 用作默认参数来强制i的早期绑定

    Unlike closure variables (like i ), default arguments are evaluated immediately when the function is defined:与闭包变量(如i )不同,定义函数时会立即评估默认参数:

     for i in range(3): def f(i=i): # <- right here is the important bit return i functions.append(f)

    To give a little bit of insight into how/why this works: A function's default arguments are stored as an attribute of the function;深入了解它的工作方式/原因:函数的默认参数存储为函数的属性; thus the current value of i is snapshotted and saved.因此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,)
  • Use a function factory to capture the current value of i in a closure使用函数工厂在闭包中捕获i的当前值

    The root of your problem is that i is a variable that can change.你的问题的根源是i是一个可以改变的变量。 We can work around this problem by creating another variable that is guaranteed to never change - and the easiest way to do this is a closure :我们可以通过创建另一个保证永远不会改变的变量来解决这个问题——最简单的方法是闭包

     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)
  • Use functools.partial to bind the current value of i to f使用functools.partiali的当前值绑定到f

    functools.partial lets you attach arguments to an existing function. functools.partial允许您将参数附加到现有函数。 In a way, it too is a kind of function factory.在某种程度上,它也是一种功能工厂。

     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)

Caveat: These solutions only work if you assign a new value to the variable.警告:这些解决方案仅在您为变量分配新值时才有效。 If you modify the object stored in the variable, you'll experience the same problem again:如果您修改存储在变量中的对象,您将再次遇到同样的问题:

>>> 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]

Notice how i still changed even though we turned it into a default argument!请注意,即使我们将它变成了默认参数, i仍然是如何改变的! If your code mutates i , then you must bind a copy of i to your function, like so:如果您的代码发生了变异i ,那么您必须将i副本绑定到您的函数,如下所示:

  • def f(i=i.copy()):
  • f = f_factory(i.copy())
  • f_with_i = functools.partial(f, i.copy())

To add onto @Aran-Fey's excellent answer, in the second solution you might also wish to modify the variable inside your function which can be accomplished with the keyword nonlocal :要添加到@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))

You can try like this:你可以这样尝试:

l=[]
for t in range(10):
    def up(y):
        print(y)
    l.append(up)
l[5]('printing in 5th function')

You have to save the each of the i value in a separate space in memory eg:您必须将每个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]

I am currently developing a module for this, but what you would want to do is create a script that writes the function in another python file like this我目前正在为此开发一个模块,但您想要做的是创建一个脚本,将函数写入另一个 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")

This does only work once and you would need to develop some code to check for existing or removed functions once the list has been generated.这只会工作一次,一旦生成列表,您将需要开发一些代码来检查现有或已删除的功能。

just modify the last line for只需修改最后一行

functions.append(f())

Edit: This is because f is a function - python treats functions as first-class citizens and you can pass them around in variables to be called later on.编辑:这是因为f是一个 function - python 将函数视为一等公民,您可以将它们传递给稍后调用的变量。 So what your original code is doing is appending the function itself to the list, while what you want to do is append the results of the function to the list, which is what the line above achieves by calling the function.因此,您的原始代码所做的是将 function 本身附加到列表中,而您想要做的是将 append 的结果添加到列表中,这是上面的行通过调用 function 实现的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM