词法闭包是如何工作的?

新手上路,请多包涵

当我调查我在 Javascript 代码中遇到的词法闭包问题时,我在 Python 中遇到了这个问题:

 flist = []

for i in xrange(3):
    def func(x): return x * i
    flist.append(func)

for f in flist:
    print f(2)

请注意,此示例有意识地避免了 lambda 。它打印出“4 4 4”,这是令人惊讶的。我期待“0 2 4”。

这个等效的 Perl 代码是正确的:

 my @flist = ();

foreach my $i (0 .. 2)
{
    push(@flist, sub {$i * $_[0]});
}

foreach my $f (@flist)
{
    print $f->(2), "\n";
}

打印“0 2 4”。

你能解释一下区别吗?


更新:

问题 不在于 i 是全局的。这显示相同的行为:

 flist = []

def outer():
    for i in xrange(3):
        def inner(x): return x * i
        flist.append(inner)

outer()
#~ print i   # commented because it causes an error

for f in flist:
    print f(2)

正如注释行所示, i 在这一点上是未知的。尽管如此,它仍会打印“4 4 4”。

原文由 Eli Bendersky 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 412
2 个回答

Python 实际上按照定义运行。创建了 三个独立的函数,但它们每个都有 定义它们的环境的闭包——在本例中为全局环境(如果循环位于另一个函数内,则为外部函数的环境)。不过,这正是问题所在——在这种环境中, i 被修改 了,闭包都 指向同一个 i

这是我能想到的最佳解决方案——创建一个函数创建器并调用 _它_。这将为创建的每个函数强制使用 不同的环境,每个函数中都有 不同的 i

 flist = []

for i in xrange(3):
    def funcC(j):
        def func(x): return x * j
        return func
    flist.append(funcC(i))

for f in flist:
    print f(2)

当您混合使用副作用和函数式编程时,就会发生这种情况。

原文由 Claudiu 发布,翻译遵循 CC BY-SA 4.0 许可协议

循环中定义的函数不断访问相同的变量 i 而其值发生变化。在循环结束时,所有函数都指向同一个变量,该变量保存循环中的最后一个值:效果如示例中所报告的那样。

为了评估 i 并使用它的值,一个常见的模式是将它设置为参数默认值:参数默认值在执行 def 语句时评估,因此值循环变量被冻结。

以下工作按预期进行:

 flist = []

for i in xrange(3):
    def func(x, i=i): # the *value* of i is copied in func() environment
        return x * i
    flist.append(func)

for f in flist:
    print f(2)

原文由 piro 发布,翻译遵循 CC BY-SA 2.5 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题