Python 2.7 函数是不是记住值而不是引用?关闭怪异

Posted

技术标签:

【中文标题】Python 2.7 函数是不是记住值而不是引用?关闭怪异【英文标题】:Have Python 2.7 functions remember value and not reference? Closure WeirdnessPython 2.7 函数是否记住值而不是引用?关闭怪异 【发布时间】:2011-10-07 21:41:19 【问题描述】:

我试图从一个函数返回一个函数列表,每个函数都使用外部范围的变量。这是行不通的。这是一个演示正在发生的事情的示例:

a = []
for i in range(10):
    a.append(lambda x: x+i)
a[1](1) # returns 10, where it seems it should return 2

为什么会发生这种情况,如何在 python 2.7 中解决它?

【问题讨论】:

【参考方案1】:

i 每次都引用同一个变量,所以 i 在所有 lambda 表达式中都是 9,因为这是循环结束时 i 的值。最简单的解决方法是使用默认参数:

lambda x, i=i: x+i

这会将循环的 i 的值绑定到 lambda 定义时的局部变量 i

另一种解决方法是定义一个定义另一个 lambda 的 lambda,并调用第一个 lambda:

(lambda i: lambda x: x+i)(i)

如果您考虑这一点,这种行为会更有意义:

def outerfunc():

    def innerfunc():
        return x+i

    a = []
    for i in range(10):
        a.append(innerfunc)
    return a

这里,innerfunc 定义了一次,因此直观的感觉是您只使用一个函数对象,并且您不会期望循环创建十个不同的闭包。使用 lambda,它看起来 不像函数只定义一次,看起来你每次都在循环中重新定义它,但实际上它在功能上与 long 相同版本。

【讨论】:

嗯。真的没有办法让每个 lambda 保持自己的价值吗?我认为这就是结束的全部意义。 @Chironex,闭包在变量名而不是值上 所有声称具有闭包功能的编程语言都是这种情况吗? @Chironex - 这是他们的观点,但它不像你想象的那样工作! :) @phkahler:您正在为新定义的函数参数分配默认值。然后该值将存储在函数对象中,而不是使用词法范围规则。【参考方案2】:

因为当您定义匿名函数(lambda 表达式)时,i 没有被评估,而是在它被调用时。您可以通过在a[1](1) 之前添加del i 来查看这一点:您将在a[1](1) 行上获得NameError: global name 'i' is not defined

你需要每次都将 i 的值固定到 lambda 表达式中,如下所示:

a = [lambda x, i=i: x+i for i in range(10)]
a[1](1) # returns 2

【讨论】:

【参考方案3】:

另一种更通用的解决方案 - 也没有 lambda:

import operator
from functools import partial
a = []
for i in range(10):
    a.append(partial(operator.add, i))
a[1][(1) # returns 2

这里的关键方面是functools.partial。

【讨论】:

partial(operator.add, i) 等价于(lambda i: lambda *args, **kwargs: operator.add(i, *args, **kwargs))() 但这绝对更漂亮,并且内置函数在可能的情况下比 lambda 表达式更好。我不会说这没有 lambdas:如果操作很复杂,它仍然会涉及某种类型的函数定义。 我认为最后一对括号中应该有一个i。至于 lambdas:它至少没有 (double) lambdas 构造;) 而其中的 double 让我真的不寒而栗 :) @agf:是的,5 分钟过去了 :)

以上是关于Python 2.7 函数是不是记住值而不是引用?关闭怪异的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 2 中,无论列表的内容如何,​​如何按值而不是引用复制复杂嵌套元素的列表

如果 Swift 通过值而不是引用传递,为啥我可以通过将 UIView 传递给函数来在 Swift 中操作 UIView 的属性? [复制]

Python中的函数更改列表值而不是变量值[重复]

按值而不是按引用复制对象

c#基于值而不是引用调度扩展方法

如何按值而不是按引用将数组添加到列表中?