一、一道面试题
给出以下代码的pirnt结果
1
2
3
4
li = [lambda : x for x in range(10)]
ret = li[0]()
print(ret)
答案是9,出乎意料的是li内的所有匿名函数的结果都是9。
二、闭包产生的影响
Python 闭包的后期绑定导致的 late binding,这意味着在 闭包中的变量是在内部函数被调用的时候被查找
。所以结果是,当任何 li 内的匿名函数被调用时,x的值是在它被调用时的周围作用域中查找,无论哪个lambda函数被调用,for 循环都已经完成了,x 最后的值是9,因此,每个lambda函数返回的值都是 9。
我们从下图中能更好的理解这个列表推导式
整体上li是一个[ xxx for x in range(10) ]
列表推导式,重点是这个xxx
又是一个匿名函数表达式lambda :x
。 这个函数在调用时会返回x的值。
因此li表达式的第一步是生成了10个匿名表达式, 在这一步循环是已经结束了的, 循环的目的是生成10个匿名函数。如图:
但是匿名函数的内部变量又使用了循环产生的变量x, x与匿名函数形成了一个闭包。 循环结束x的值只能是9,所以在每个函数调用时,结果都是9。
通过以下等同的代码,我们可以明确的看出闭包的形成:
1
2
3
4
5
li = list()
for x in range(10):
def test():
return x
li.append(test)
此外,我们可以通过变量赋值的方式,保存每次循环时x的值,就可以得到正确的结果了:
1
li = [lambda j=x: j for x in range(10)]
三、lambda 的简单应用
1. 什么是lambda
匿名函数表达式,目的是简略函数的定义,使用简单的一行代码就实现一个函数功能。如果超过2行代码,就不要使用lambda了。lambda表达式的意义就是简洁明了。过于复杂不利于代码的可读性。
2. 无参数的lambda
上例就是一个无参的匿名函数。
3. 带参数的lambda
lambda x: x+1 这个匿名函数的功能是返回每次传入的参数+1,如下图:
4. 带表达式的lambda
上例li = [lambda j=x: j for x in range(10)]
就是一个带有表达式的匿名函数。但是匿名函数不要过于复杂化。大多时候太复杂话不利于代码的可读性,同时又容易出现不易察觉bug。所以炫技要适当,不然就容易翻车了,还是老老实实写简单易懂的代码吧。