词法闭包是如何工作的?

2024-05-11 11:23:17 发布

您现在位置:Python中文网/ 问答频道 /正文

在我调查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”,这是令人惊讶的。我希望是“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在这一点上是未知的。尽管如此,它还是打印了“444”。


Tags: 代码inforreturnmydefjavascriptfunc
3条回答

下面是如何使用functools库(我不确定在提出问题时是否可用)来实现这一点。

from functools import partial

flist = []

def func(i, x): return x * i

for i in xrange(3):
    flist.append(partial(func, i))

for f in flist:
    print f(2)

如预期,输出0 2 4。

循环中定义的函数在其值更改时继续访问同一变量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)

Python的行为实际上是按定义的。创建了三个独立的函数,但每个函数都具有在中定义的环境的闭包—在本例中,是全局环境(如果循环放在另一个函数中,则是外部函数的环境)。不过,这正是问题所在,在这个环境中,i发生了变异,所有的闭包都指向同一个i

这是我能想到的最好的解决方案-创建一个函数creater并调用来代替它。这将为每个创建的函数强制不同的环境,每个函数中有一个不同的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)

当你把副作用和函数式编程混合在一起时,就会发生这种情况。

相关问题 更多 >