Python——函数的高级话题(2)

44 篇文章 0 订阅
44 篇文章 21 订阅

匿名函数:lambda

除了def语句之外,Python还提供了一种生成函数对象的表达式。由于它与LISP语言中的一个工具很相似,所以称为lambda。
就像def一样,这个表达式创建了一个之后能够调用的函数,但是它返回了一个函数而不是将这个函数赋值给一个变量名。这也就是lambda有时叫做匿名(也就是没有函数名)的函数的原因。实际上,它们常常以一种行内进行函数定义的形式使用,或者用作推迟执行一些代码。

-------------------------------------------------------------------------------------------------------------

lambda表达式

lambda的一般形式是关键字lambda,之后是一个或多个参数(与一个def头部内用括号括起来的参数列表极其相似),紧跟的是一个冒号,之后是一个表达式:

lambda argument1,argument2,...,argumentN:expression using arguments
注意以下几点:
1.lambda是一个表达式,而不是一个语句。因为这一点,lambda能过出现在Python语法不允许def出现的地方——例如,在一个列表常量中或者函数调用的参数中。此外,作为一个表达式,lambda返回了一个值(一个新的函数),可以选择性地赋值给一个变量名。相反,def语句总是得在头部将一个新的函数赋值给一个变量名。

2.lambda的主体是一个单个的表达式,而不是代码块。因为它仅限于表达式,lambda通常要比def功能要小:你尽能够在lambda主体中封装有限的逻辑进去,连if这样的语句都不能使用。这是有意设计的——它限制了程序的嵌套:lambda是一个为编写简单的函数而设计的,而def用来处理更大的任务。

我们已经知道了如何使用def语句创建函数:

>>> def func(x,y,z):return x+y+z

>>> func(2,3,4)
9
但是,能够使用lambda表达式达到相同的效果,通过明确地将结果赋值给一个变量名,之后就能够通过这个变量名调用这个函数。
>>> f = lambda x,y,z:x+y+z
>>> f(2,3,4)
9
这里的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 = knights()
>>> act('robin')
'Sir robin'
-------------------------------------------------------------------------------------------------------------
为什么使用lambda

通常来说,lambda起到了一种函数速写的作用,允许在使用的代码中内嵌入一个函数的定义。
lambda通常用来编写跳转表(jump table),也就是行为的列表或字典,能够按照需要执行相应的动作,如下段代码所示:

>>> L = [lambda x:x**2,
	 lambda x:x**3,
	 lambda x:x**4]
>>> for f in L:
	print(f(2))

	
4
8
16
>>> print(L[0](3))
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))

	
4
8
16
>>> print(L[0](3))
9
实际上,可以用Python中的字典或者其他的数据结构来构建更多种类的行为表,从而做同样的事情。看下例:
>>> key = 'got'
>>> {'already':(lambda :2+2),
     'got':(lambda:2*4),
     'one':(lambda:2**6)}[key]()
8
这里,当Python创建这个字典的时候,每个嵌套的lambda都生成并留下了一个在之后能够调用的函数。通过键索引来取回其中一个函数,而括号使去除的函数被调用。

如果不是用lambda做这种工作,需要使用三个文件中其他地方出现过的def语句来替代。而lambda提供了一种特别有用的可以在单个情况出现的函数:如果这里的三个函数不会在其他地方使用到,那么将它们的定义作为lambda嵌入到字典中就是很合理了。
-------------------------------------------------------------------------------------------------------------
如何(不要)让Python代码变得晦涩难懂

由于lambda的主体必须是单个表达式(而不是一些语句),由此可见仅能将有限的逻辑封装到一个lambda中。如果你知道在做什么,那么你就能在Python中作为基于表达式等效的写法编写足够多的语句。

例如,要在一个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(1,2)
1
>>> lower(2,1)
1
>>> lower('bb','aa')
'aa'
此外,如果需要在lambda函数中执行循环,能够嵌入map调用或列表解析表达式这样的工具来实现。
>>> import sys
>>> showall = lambda x:map(sys.stdout.write,x)
>>> t = showall(['spam\n','toast\n','eggs\n'])
>>> t
<map object at 0x03B301F0>
>>> list(t)
spam
toast
eggs
[5, 6, 5]
如上,map返回一个可迭代对象,可以通过循环逐个获得结果,也可使用内置list函数强制生成所有结果。可以看到,list(t)调用先输出每句话,再返回t的值
>>> showall = lambda x:list(map(print,x))
>>> t = showall(['spam\n','toast\n','eggs\n'])
spam

toast

eggs

>>> t
[None, None, None]
再看上述代码,将print函数映射到列表,这时打印出了三句话,但是t的三个返回值都是None。这就说明了print函数在底层虽然是sys.stdout.write实现的(之前介绍过),但还是有差别的,print函数不返回任何值,而sys.stdout.write返回了输出的字符数。


也可以使用列表解析实现上述功能:

>>> showall = lambda x:[sys.stdout.write(line) for line in x]
>>> t = showall(['bright\n','side\n','of\n','life\n'])
bright
side
of
life
>>> t
[7, 5, 3, 5]
这些技巧必须在万不得已的情况下才使用。因为一不小心,它们就会导致不可读的Python代码。一般来说,简洁优于复杂,明确优于晦涩,而且一个完整的语句要比神秘的表达式要好。但另一方面,适度的使用这些技术是很有用处的。
-------------------------------------------------------------------------------------------------------------

嵌套lambda和作用域

lambda是嵌套函数作用域查找的最大受益者。例如,下例,lambda出现在def中,并且在上层函数调用的时候,嵌套的lambda能够获取到上层函数作用域中的变量名x的值。

>>> def action(x):
	return (lambda y:x+y)

>>> act = action(99)
>>> act
<function action.<locals>.<lambda> at 0x032C1198>
>>> 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函数中的变量。不过,处于对可读性的要求,通常来说,尽量避免使用嵌套的lambda
=======================================================================

在序列中映射函数:map

程序对列表和其他序列常常要做的一件事就是对每一个元素进行一个操作并把其结果集合起来。例如,在一个列表counter中更新所有的数字,可以简单地通过一个for循环来实现。

>>> counters=[1,2,3,4]
>>> updated=[]
>>> for x in counters:
	updated.append(x+10)

	
>>> updated
[11, 12, 13, 14]
因为这是一个如此常见的操作,Python实际上提供了一个内置的工具,map函数会对一个序列对象中的每一个元素应用被传入的函数,并且返回一个包含了所有函数调用结果的一个列表。如下:

>>> def inc(x):return x+10

>>> list(map(inc,counters))
[11, 12, 13, 14]
这里,我们传入了一个用户定义的函数,从而对列表中的每一个元素应用这个函数。不过,不要忘记map在Python3.0中是一个可迭代对象,因此,在这里,一个列表调用用来迫使它生成所有的结果以显示。


由于map期待传入一个函数,它恰好是lambda通常出现的地方之一:

>>> list(map(lambda x:x+10,counters))
[11, 12, 13, 14]
在多编写了一些代码之后,就可以自己编写一个一般的映射工具了。
>>> def mymap(func,seq):
	res = []
	for x in seq:
		res.append(func(x))
	return res
我们就可以用内置函数或我们自己的对等形式将其映射到一个序列:
>>> list(map(inc,[1,2,3]))
[11, 12, 13]
>>> mymap(inc,[1,2,3])
[11, 12, 13]
但是要注意的是,map是内置函数,它肯定有一些性能方面的优势,比我们自己写的for循环更快。
此外,map还有更高级的使用方法。例如,提供多个序列作为参数,它能够并行返回分别以每个序列中的元素作为函数对应参数得到的结果的列表。
>>> pow(3,4)
81
>>> list(map(pow,[1,2,3],[2,3,4]))
[1, 8, 81]
=======================================================================
函数式编程工具:filter和reduce
在Python内置函数中,map函数是用来进行函数式编程的这类工具中最简单的内置函数代表:函数式编程的意思就是对序列应用一些函数的工具。例如,基于某一测试函数过滤出一些元素(filter),以及对每对元素都应用函数并运行到最后结果(reduce)。
下面这个filter的调用实现了从一个序列中挑选出大于0的元素。

>>> list(range(-5,5))
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
>>> list(filter((lambda x:x>0),range(-5,5)))
[1, 2, 3, 4]
序列中的元素若其返回值为真的话,将会被键入到结果的列表中。就像map,这个函数也能够概括地用一个for循环来等效,但是他也是内置的,运行比较快。

>>> res = []
>>> for x in range(-5,5):
	if x>0:
		res.append(x)

		
>>> res
[1, 2, 3, 4]
reduce在Python3.0中要复杂一点,它位于functools模块中,它接受一个迭代器来处理,但是,它自身不是一个迭代器,它返回一个单个的结果。这里是两个reduce调用,计算了在一个列表中所有元素加起来的和以及乘起来的乘积。
>>> from functools import reduce
>>> reduce((lambda x,y:x+y),[1,2,3,4])
10
>>> reduce((lambda x,y:x*y),[1,2,3,4])
24
每一步,reduce传递了当前的和或乘积以及列表中下一个元素,传给列出的lambda函数。默认,序列中的第一个元素初始化了起始值。这里是对第一个调用的for循环的等效,在循环中是用了额外的代码
>>> L = [1,2,3,4]
>>> res = L[0]
>>> for x in L[1:]:
	res = res +x

	
>>> res
10
编写自己的reduce版本实际上相当直接。如下的函数模拟内置函数的大多数行为,并且帮助说明其一般性的运作:

>>> def myreduce(function,sequence):
	tally = sequence[0]
	for nex in sequence[1:]:
		tally = function(tally,nex)
	return tally

>>> myreduce((lambda x,y:x+y),[1,2,3,4])
10
>>> myreduce((lambda x,y:x*y),[1,2,3,4])
24
另外,可以看看内置的operator模块,其中提供了内置表达式对应的函数,并且对于函数式工具来说,它使用起来很方便。
>>> import operator,functools
>>> functools.reduce(operator.add,[2,4,6])
12
>>> functools.reduce((lambda x,y:x+y),[2,4,6])
12

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值