闭包陷阱

闭包的常见特征是在函数中定义子函数,并且子函数访问父函数中的局部变量,有时子函数还可作为返回值返回。

Python 也支持闭包,同其他语言类似,闭包中外部变量的值是在运行时确定的。如果在闭包定义到执行期间,外部变量发生变化,会影响到闭包的执行结果。本人最近遇到这个问题,遍历序列的过程中,在每个循环体里定义一个子函数,它使用的外部变量实际上是用于接收序列中的值。下面是一个简化的例子:

def parent():
    funcs = []
    for d in xrange(4):
        def closure():
            print d
        funcs.append(closure)
    for func in funcs:
        func()

上面的例子里,本来期待 4 个闭包分别输出 0, 1, 2, 3,但实际输出 3, 3, 3, 3。原因就是闭包依赖的变量 d在执行时的值为 3

其中一种解决方法是,在子函数的定义里增加额外的输入参数,把依赖外部变量的代码改为依赖输入参数。利用标准库 functoolspartial方法,我们可以确定输入参数的值,并得到一个新的函数对象,从而解决上述问题。下面是一个小例子:

import functools

def parent():
    funcs = []
    def closure(j):
        print j
    for d in xrange(4):
        func = functools.partial(closure, d)
        funcs.append(func)
    for func in funcs:
        func()

实际上,第二个例子比起第一个例子还减少了函数对象的定义,整个过程中只定义了 1 次,可能在性能上有优势。对于其他动态语言,通常也有类似的机制,例如 JavaScript 的函数对象有 bind()方法。

Advertisements

2 thoughts on “闭包陷阱

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s