除了def语句之外,Python还提供了一种生成函数对象的表达式形式。由于它与LISP语言中的一个工具很相似,所以称为lambda。就像def一样,这个表达式创建了一个之后能够调用的函数,但是它返回了一个函数而不是将这个函数赋值给一个变量名。这也就是lambda有时叫做匿名函数的原因。实际上,他们常常以一种行内进行函数定义的形式使用,或者用作推迟执行一些代码。
lambda表达式
lambda的一般形式是关键字lambda,之后是一个或多个参数(与一个def头部内用括号括起来的参数列表及其相似),紧跟的是一个冒号,之后是一个表达式:
lambda argument1,argument2,... argumentN:expression using argument
由lambda表达式所返回的函数对象与由def创建并复制后的函数对象工作起来是完全一样的,但是lambda由一些不同之处让其在扮演特定的角色时很有用。
-
lambda是一个表达式,而不是一个语句。因为这一点,lambda能够出现在Python语法不允许def出现的地方——例如,在一个列表常量中或者函数调用的参数中。此外,作为一个表达式,lambda返回了一个值(一个新的函数),可以选择性的赋值给一个变量名。相反,def语句总是得在头部将一个新的函数赋值给一个变量名,而不是讲这个函数作为结果返回。
-
lambda的主体是一个单个的表达式,而不是一个代码块。这个lambda的主体简单得就好像放在def主体的return语句中的代码一样。简单地将结果写成一个顺畅的表达式,而不是明确的返回。因为它仅限于表达式,lambda通常要比def功能要小:你仅能够在lambda主体中封装有限的逻辑进去,连if这样的语句都不能够使用。 这是有意设计的——它限制了程序的嵌套:lambda是一个为编写简单的函数而设计的,而def用来处理更大的任务。
除了这些差别,def 和 lambda都能够做同样种类的工作。例如,我们见到了如何使用def语句创建函数。
# ###################### 普通函数 ###################### # 定义函数(普通方式) def func(arg): return arg + 1 # 执行函数 result = func(123)
但是,能够使用lambda表达式达到相同的效果,通过明确地将结果赋值给一个变量名,之后就能够通过这个变量名调用这个函数。
# ###################### lambda ###################### # 定义函数(lambda表达式) my_lambda = lambda arg : arg + 1 # 执行函数 result = my_lambda(123)
这里的f被赋值给一个lambda表达式创建的函数对象。 这也就是def所完成的任务,只不过def的赋值是自动进行的。
默认参数也能够在lambda参数中使用,就像在def中使用一样。
>>> x = (lambda a="fee", b="fie", c="foe": a + b + c) >>> x("wee") ‘weefiefoe‘
在lambda主体中的代码想在def内的代码一样都遵循相同的作用于查找法则。lambda表达式引入的一个本地作用域更像一个嵌套的def语句,将会自动从上层函数中、模块中 以及内置作用域中(通过LEGB法则)查找变量名。
>>> def knights(): ... title = "Sir" ... action = (lambda x: title + ‘ ‘ + x) ... return action ... >>> act = knight() >>> act(‘robin‘) ‘Sir robin‘
在Python 2中,变量名title的值通常会修改为通过默认参数的值传入。
为什么使用lambda
通常来说,lambda起到了一种函数速写的作用,允许在使用的代码内嵌入一个函数的定义。他们完全是可选的(你总是能够使用def来替代它们),但是你仅需要嵌入小段可执行代码的情况下它们会带来一个更简洁的代码结构。
例如,我们在稍后会看到回调处理器,它常常在一个注册调用(registration call)的参数列表中编写成单行的lambda表达式,而不是使用在文件其他地方的一个def来定义,之后引用那个变量名。
lambda通常用来编写跳转表(jump table),也就是行为的列表或字典,能够按照需要执行相应的动作。如下段代码所示。
L = [lambda x: x**2, lambda x: x**3, lambda x: x**4] for f in L: print(f(2)) # prints 4, 8, 16 print(L[0](3)) # prints 9
当需要把小段的可执行代码编写进def语句从语法上不能编写进的地方时,lambda表达式作为def的一种速写来说是最为有用的。 例如,这种代码片段,可以通过在列表常量中嵌入lambda表达式创建一个含有三个函数的列表。一个def是不会再列表常量中工作的,因为它是一个语句,而不是一个表达式。 对等的def代码可能需要在想要使用的环境之外有临时性的函数名称和函数定义。
def f1(x): return x**2 def f2(x): return x**3 def f3(x): return x**4 L = [f1, f2, f3] for f in L: print(f(2)) # prints 4, 8, 16 print(L[0](3)) # prints 9
实际上,我们可以使用Python中的字典或者其他的数据结构来构建更多种类的行为表,从而做同样的事情。 下面是以交互提示模式给出的另一个例子:
>>> key = ‘got‘ >>> {‘already‘: (lambda: 2 + 2), ... ‘got‘: (lambda: 2 * 4), ... ‘one‘: (lambda: 2 ** 6)}[key]() 8
这里,当Python常见这个字典的时候,每个嵌套的lambda都生成并留下了一个在之后能够调用的函数。通过键索引来取回其中一个函数,而括号使去除的函数被调用。 与在之前向你展示的if语句的扩展用法相比,这样编写代码可以使字典成为更加通用的多路分支工具。
如果不是用lambda做这种工作,需要使用三个文件中其他地方出现过的def语句来替代,也就是在这些函数将会使用的那个字典外的某处需要定义这些函数。
>>> def f1(): return 2 + 2 ... >>> def f2(): return 2 * 4 ... >>> def f3(): return 2 ** 6 ... >>> key = ‘one‘ >>> {‘already‘: f1, ‘got‘: f2, ‘one‘: f3}[key]() 64
同样,会实现相同的功能,但是def也许会出现在文件中的任意位置,即使它们之后很少的代码。类似刚才lambda的代码,提供了一种特别有用的可以在单个情况出现的函数:如果这里的三个函数不会在其他的地方使用到,那么将它们定义作为lambda嵌入在字典中就很合理了。不仅如此,def格式要求为这些小函数创建变量名,这些变量名也许会与这个文件中的其他变量名发生冲突(也可能不会,但总是可能的)。
如何(不要)让Python代码变得晦涩难懂
由于lambda的主体必须是个表达式(而不是一些语句),由此可见仅能将有限的逻辑封装到一个lambda中。如果你知道在做什么,那么你就能在Python中作为基于表达式等效的写法编写足够多的语句。
例如,如果你希望在lambda函数中进行print,直接编写sys.stdout.write(str(x) + "\n") 这个表达式,而不是使用print(x)这样的语句。 类似地,要在一个lambda中潜逃逻辑,可以使用if/else三元表达式,或者对等的但需要些技巧的and/or组合。正如我们前面所了解到的,如下语句:
if a: b else: c
能够由以下的概括等效的表达式来模拟:
b if a else c ((a and b) or c)
因为这样类似的表达式能够放在lambda中,所以它们能够在lambda函数中来实现选择逻辑。
>>> lower = (lambda x, y: x if x < y else y) >>> lower(‘bb‘, ‘aa‘) ‘aa‘ >>> lower(‘aa‘, ‘bb‘) ‘aa‘
此外,如果需要在lambda函数中执行循环,能够嵌入map调用或列表解析表达式这样的工具来实现。
>>> import sys >>> showall = lambda x: list(map(sys.stdout.write, x)) >>> t = showall([‘spam\n‘, ‘toast\n‘, ‘eggs\n‘]) spam toast eggs >>> showall = lambda x: [sys.stdout.write(line) for line in x] # 列表解析 >>> t = showall([‘spam\n‘, ‘toast\n‘, ‘eggs\n‘]) spam toast eggs
这些技巧必须在万不得已的情况下才使用。一不小心,它们就会导致不可读(也成为晦涩难懂)的Python代码。 一般来说,简洁优于复杂,明确优于晦涩,而且一个完整的语句要比神秘的表达式要好。 这就是为什么lambda仅限于表达式。如果你有更负责的代码要编写,可使用def, lambda针对较小的一段内联代码。 从另一个方面来说,你也会发现湿度的使用这些技术是很有用处的。
嵌套lambda和作用域
lambda是嵌套函数作用域查找(LEGB原则中的E)的最大受益者。 例如,在下面的例子中,lambda出现在def中(很典型的情况),并且在商城函数调用的时候,嵌套的lambda能够获取到上层函数作用域中的变量名x的值。
>>> def action(x): return (lambda y: x + y) >>> act = action(99) >>> act <function action.<locals>.<lambda> at 0x0000014EF59F4C80> >>> act(2) 101
在之前讲关于嵌套函数作用域的讨论没有标明的就是lambda也能够获取任意上层lambda中的变量名。 这种情况有些隐晦,但是想象一下,如果我们上一个例子中高端def换成一个lambda。
>>> action = (lambda x:(lambda y: x + y)) >>> act = action(99) >>> act(3) 102 >>> ((lambda x: (lambda y: x + y))(99))(4) 103
这里嵌套的lambda结构让函数在调用时创建了一个函数。无论以上那种情况,嵌套的lambda代码都能够获取上层lambda函数中的变量x。这可以工作,但是这种代码让人相当费解。处于可读性的要求,通常来说,最好避免使用嵌套的lambda。