先看看这样一个经典的栗子:
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?!为什么不是想象中的那个样子呢?
在了解这个问题的答案之前,我们先来看看,什么是闭包。
闭包定义:
- 闭包是一个嵌套函数
- 闭包必须返回嵌套函数
- 嵌套函数必须引用一个外部的非全局的局部自由变量
举个栗子:
# 嵌套函数但不是闭包
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()
闭包具有以下优点:
- 避免使用全局变量
- 可以提供部分数据的隐藏
- 可以提供更优雅的面向对象实现
介绍完了闭包,我们再回过头来看看开头的那个栗子,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]的原因。