Python 在循环中创建函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3431676/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 11:03:09  来源:igfitidea点击:

Creating functions in a loop

pythonfunction

提问by sharvey

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?

为什么会发生这种情况,我应该怎么做才能获得分别输出 0、1 和 2 的 3 个不同函数?

采纳答案by Alex Martelli

You're running into a problem with late binding-- each function looks up ias late as possible (thus, when called after the end of the loop, iwill 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 iin i=iis a default value for argument name i, which is the left-hand iin i=i) are looked up at deftime, not at calltime, so essentially they're a way to specifically looking for early binding.

默认值(右边的iini=i是参数 name 的默认值i,它是左边的iin i=i)是在def时间而不是call时间上查找的,所以本质上它们是一种专门寻找早期绑定的方法。

If you're worried about fgetting 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 defstatement.

并在您的循环中使用f = make_f(i)而不是def语句。

回答by Aran-Fey

The Explanation

说明

The issue here is that the value of iis not saved when the function fis created. Rather, flooks up the value of iwhen it is called.

这里的问题是在创建i函数时没有保存的值f。相反,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_varhas changed after the function was declared. The same thing is happening in your own code: By the time you call f, the value of ihas changed and been set to 2.

当您阅读此代码时,您当然会期望它打印“bar”,而不是“foo”,因为在global_var声明函数后, 的值已更改。同样的事情发生在您自己的代码中:当您调用 时f, 的值i已更改并设置为2

The Solution

解决方案

There are actually many ways to solve this problem. Here are a few options:

其实有很多方法可以解决这个问题。这里有几个选项:

  • Force early binding of iby using it as a default argument

    Unlike closure variables (like i), default arguments are evaluated immediately when the function is defined:

    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 currentvalue of iis snapshotted and saved.

    >>> 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 iin a closure

    The root of your problem is that iis a variable that can change. We can work around this problem by creating anothervariable 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.partialto bind the current value of ito f

    functools.partiallets you attach arguments to an existing function. 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)
    
  • 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绑定的当前值if

    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)
    

Caveat:These solutions only work if you assigna new value to the variable. If you modifythe 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 istill changed even though we turned it into a default argument! If your code mutatesi, then you must bind a copyof ito your function, like so:

注意i即使我们把它变成了默认参数,它仍然发生了怎样的变化!如果你的代码发生变异i,那么你就必须绑定一个副本i你的功能,如下所示:

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