先看看这样一个经典的栗子:

def multiplier():
    return [lambda x: x*i for i in range(3)]

print([func(2) for func in multiplier()])

这个栗子就是一个简单的闭包,那它的输出结果会是多少呢?小脑瓜儿飞速一转,按照预想可能会是[0, 2, 4]吧?但是!通过实际的运行我们可以看到输出为[4, 4, 4],咦?!why?!为什么不是想象中的那个样子呢?
在了解这个问题的答案之前,我们先来看看,什么是闭包。

闭包定义:

  1. 闭包是一个嵌套函数
  2. 闭包必须返回嵌套函数
  3. 嵌套函数必须引用一个外部的非全局的局部自由变量

举个栗子:

# 嵌套函数但不是闭包
def nested_func():
    def func():
        print('i am nested func %s' % nested_func.__name__)
    func()

nested_func()

# 闭包函数
def closure():
    var = 'hello world'  # 非全局局部变量

    def cloe():
        print(var)  # 引用var

    return cloe  # 返回内部函数


cl = closure()
cl()

闭包具有以下优点:

  1. 避免使用全局变量
  2. 可以提供部分数据的隐藏
  3. 可以提供更优雅的面向对象实现

介绍完了闭包,我们再回过头来看看开头的那个栗子,Python解释器对于lambda只是定义了一个匿名函数对象,保存在内存中,只有调用到这个匿名函数时才会执行内部的表达式(x*i)。而这里只有等for循环结束后才会开始调用lambda函数,此时i就等于for循环执行后的最终值2.

为了便于理解,我们将它改成这样的形式:

def multiplier():
    func_list = []
    for i in range(3):
        def lambda_func(x):
            return x * i

        func_list.append(lambda_func)

    return func_list


print([func(2) for func in multiplier()])

#output:[4, 4, 4]

这怎么理解呢,简单来说,在python里,相对而言的局部变量是绑定的值,而非局部变量绑定的是地址,而不是值本身,所以这里在函数lambda_func外for循环定义的i对于lambda_func而言就是非局部变量(处于嵌套作用域),绑定的是i所在的内存地址,随着循环的运行,i最后变成了2,所以在lambda_func调用时,i的值就是2,因此最后的结果都是x*2(即为4)。

如果我们想要达到预期(得到就过[0, 2, 4])只需做一处改动即可:

def multiplier():
    return [lambda x, i=i: x * i for i in range(3)]


print([func(2) for func in multiplier()])

#output:[0, 2, 4]

添加了一个i=i后,相当于给匿名函数添加了一个参数i,该参数位于每个lambda函数的局部作用域,(为便于区分理解也可以将参数命名为a,即为a=i,lambda内表达式为x*a),参数的默认值就是每次for循环时的i的值(0,1,2)。为便于理解也可以写成如下形式:

def multiplier():
    func_list = []
    for i in range(3):
        def lambda_func(x, i=i):
            return x * i

        func_list.append(lambda_func)

    return func_list


print([func(2) for func in multiplier()])

#output:[0, 2, 4]

不好理解的话我们可以把局部作用域的变量命名为j,即:

def multiplier():
    func_list = []
    for i in range(3):
        def lambda_func(x, j=i):
            return x * j

        func_list.append(lambda_func)

    return func_list


print([func(2) for func in multiplier()])

#output:[0, 2, 4]

这样是否就好理解些了呢。

那到最后什么是延迟绑定呢?
延迟绑定简单来说就是,只有在运行嵌套函数(比如上面的lambda_func)时,才会去引用外部变量(i),不运行时就不会去寻找绑定外部变量的值。这也就是开头那个栗子为什么是[4, 4, 4],而不是[0, 2, 4]的原因。