小能豆

词汇闭包如何工作?

javascript

当我调查 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”。


阅读 31

收藏
2024-07-11

共1个答案

小能豆

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)

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

2024-07-11