Closures in Python

在一个函数环境中定义的函数,它们的命名空间除了global环境,还有函数定义时候的local环境,例如这里:

def sqrt(a):
    def sqrt_update(x):
        return average(x, a/x)
    def sqrt_close(x):
        return approx_eq(x * x, a)
    return improve(sqrt_update, sqrt_close)
	
result = sqrt(256)

变量a所指的是sqrt这个函数的local环境中的a值。由于变量命名空间相对封闭的属性,函数中定义的函数常被成为closure(闭包)。

Late binding

Python中的closure的问题是,Python希望尽可能迟地绑定变量的值,这是为了性能考虑的。这在大多数情况下工作良好,但是有时候也会导致一些意想不到的问题。比如:

eggs = [lambda a: i * a for i in range(3)]

for egg in eggs:
    print(egg(5))

我们期望它的结果为:

0
5
10

但是,实际情况却不是这样的。由于late binding,变量i在调用的时候才从周围的命名空间中去找i对它进行赋值,而不是它被定义的时候。所以它的实际输入为:

10
10
10

可以使用pythontutor工具分步查看函数执行情况。

那么如果避免这种情况?解决的方法是将变量变得local,一个方法是通过partial函数进行currying,强制函数立刻binding:

import functools

eggs = [functools.partial(lambda i, a: i*a, i) for i in range(3)]
for egg in eggs:
    print(egg(5))

更好的方法是通过不要引入外部空间(lambda),或者外部变量来避免binding问题。如果i和a都作为lambda的参数,那么这就不是个问题了。