关于Python闭包

7

以下是我从某人关于Python闭包的博客中得到的示例。 我在Python 2.7中运行它,但得到了与我的预期不同的输出。

flist = []

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

for f in flist:
    print f(2)

我期望的输出结果是:0,2,4
但实际输出结果却是:4,4,4
有没有人能解释一下呢?
提前感谢您。

3个回答

17

在Python中,循环不会产生作用域,因此所有三个函数都在相同的i变量上关闭,并且将引用其在循环完成后的最终值,即2。

似乎我与使用Python闭包的几乎每个人都遭受过这种情况。推论是外部函数可以更改i,但内部函数不能(因为这将使i基于Python的语法规则成为本地变量而不是闭包)。

有两种方法可以解决这个问题:

# avoid closures and use default args which copy on function definition
for i in xrange(3):
    def func(x, i=i):
        return x*i
    flist.append(func)

# or introduce an extra scope to close the value you want to keep around:
for i in xrange(3):
    def makefunc(i):
        def func(x):
            return x*i
        return func
    flist.append(makefunc(i))

# the second can be simplified to use a single makefunc():
def makefunc(i):
    def func(x):
        return x*i
    return func
for i in xrange(3):
    flist.append(makefunc(i))

# if your inner function is simple enough, lambda works as well for either option:
for i in xrange(3):
    flist.append(lambda x, i=i: x*i)

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

其他读者请注意:Python 3添加了“nonlocal”关键字,允许每个“func”更改“i”的值,从而影响其他函数。在这种情况下没有用处,但如果您有几个内部函数,则可能会很方便。 - Walter Mundt
你可以使用lambda进一步简化最后一个,但这会限制func()的功能。 - Dubslow
@Dubslow 我认为这样做并不能简化它。在我看来,def 看起来更好。 - jamylak
添加了基于lambda的选项,因为它们对于一些小事情确实有意义。虽然现在我很少需要它们;在2.7中,生成器/字典/集合推导式已经消耗了它们很多的用处。 - Walter Mundt

4

您没有创建闭包。您正在生成一组每个都访问全局变量 i 的函数,第一次循环后它等于2。因此,每次函数调用都会得到 2 * 2。


谢谢您的回答。如果我想创建一个闭包来获得我期望的输出,如何更改代码?您能给我一些建议吗? - Alex.Zhang
@Alex.Zhang:好的,你确实要求解释为什么所经历的行为与你期望的不同。请参考 https://dev59.com/5WXWa4cB1Zd3GeqPRuxY#11408601 获取解决方案。 - mhawke

1
每个函数都访问全局变量ifunctools.partial来拯救:
from functools import partial
flist = []

for i in xrange(3):
    def func(x, multiplier=None):
        return x * multiplier
    flist.append(partial(func, multiplier=i))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接