高阶函数,匿名函数-Python基础连载(十三)
开篇
前面已经学习了函数以及函数的各种形式的参数。
本期要介绍的高阶函数也是函数的一种,只不过与普通的函数不同的是,高阶函数的参数可以是另外一个函数,高阶函数的返回值也可以是另外一个函数。
本期你将学习到Python
的3
个常用高阶函数:
map()
、filter()
和reduce()
。
你完全可以把高阶函数看做是一个正常的函数(本来就是),只不过其参数和返回值可以是函数。这样学习起来可能会更加轻松。
map
map
用于对某一个可迭代对象中的每一个元素施加同一种操作。
map()
有两个参数,第一个参数是一个函数
,第二个参数是一个可迭代对象
。
还是拿栗子来说明:
对于列表lis=[1,2,3]
,将列表中的每一个元素做平方操作,并将结果存入新的列表
如果采用普通的函数解答,你应该能写出如下的类似代码:
1 | lis=[1,2,3] |
而若使用map
函数,代码是这样子的:
1 | lis=[1,2,3] |
看,使用了map()
函数,免去了繁琐的for
循环操作,代码变得非常简洁了。
你可能会说:也没什么大不了的,就多了个循环呗,写就是咯~
其实,当列表中元素个数较多时,使用map()
的优势就体现出来了。
因为map()
函数的返回值是一个map object
(至于这是个啥,以后会讲到,这里结合下面的例子来理解即可),并不是存储实际元素的列表。当列表中元素个数较多时,若使用for
循环处理该问题,则会得到一个很大的列表,即使你只是想取结果列表中的一部分元素,也会将全部元素存储起来,此时会占用很多内存,造成内存资源的浪费,而使用map
则不会出现以上问题。
为了加深理解,我们来对比一下分别使用for
循环和map
函数所得结果占用的内存空间大小:
【补充】在Python
中查看变量所占内存大小的方法
1 | import sys |
其中的x
就是你的变量,sys.getsizeof(x)
的返回值就是变量x
所占内存空间大小。
现在,将最开始的问题中的lis
由lis=[1,2,3]
改为lis=list(range(1,10000+1))
,也就是lis=[1,2,3,...,9999,10000]
,其余条件不变,重新作答。
[1].使用for
循环的方法
1 | import sys |
输出
1 | 循环方法所得结果占用内存: 87624 |
[2].使用map()
函数
1 | import sys |
输出
1 | 循环方法所得结果占用内存: 56 |
对比来看,使用map()
函数所占内存更少。
如果想要取到map()
的结果,可以使用list()
将其返回值包裹,也就是将其返回值转为列表类型,这样就能得到和【for循环方法】中的列表new
一样的结果了。
当然,你可以选择只取前n
个,下面的函数就是用于选取前n
个元素:
1 | def get_item(n): |
比如果只想获取前5
个元素,就可以这样调用函数:
1 | get_item(5) |
得到结果
1 | 1 4 9 16 25 |
此时,其余未被选取的元素并不会占用内存。
这样就避免了内存资源的浪费。
【温馨提示】如果你尝试打印【for循环方法】中的列表new
,你的IDLE
可能会卡死,因为数据量有点大。
接下来要讲的filter
和reduce
的原理和map
差不多,大家注意对照着学习。
filter
filter
用于从某一个可迭代对象中筛选满足特定条件的元素。
举个例子,求出1
到20
以内所有能被3
整除的数字,要求使用filter
我们可以这样写:
1 | lis=[i for i in range(1,20+1)] |
输出
1 | [3, 6, 9, 12, 15, 18] |
在上面的代码中,我们定义了一个函数func
,用于判断一个数字是不是可以被3
整除,如果能,就返回True
,如果不能被3
整除,此时就没有指定返回值,那么返回值就是默认值None
,而None
的布尔值为False
,也就“相当于”在不能被3
整除时返回了False
。
接下来使用了filter
求解,filter
函数的第一个参数是函数func
,第二个参数是可迭代对象lis
。filter(func,lis)
的作用就是对lis
中的每一个元素都施加func
函数内的操作,也就是把lis
中的每一个元素都作为参数传入func
中。如果返回值为True
,就说明该元素满足筛选条件,于是被选中,否则该元素将不会被选中。最后,filter
函数会返回一个filter object
,你同样可以使用上面介绍的map
中的操作方法来操作它,这里就不再重复说明了。
reduce
reduce
在使用前需要写一句代码:
1 | from functools import reduce |
它的作用是引入reduce
。由于我们还没有讲到模块和包,因此如果你是纯小白第一次接触,只需了解在使用reduce
之前需要写这么一句代码就可以了。
reduce
函数同样有两个参数,第一个参数位置需要传入一个函数,第二个参数位置需要传入一个可迭代对象。
reduce
的作用是对可迭代对象中的元素做*累计*操作并返回累计值。这个累计,可以是累加、累乘等等。
比如可迭代对象为lis=[1,2,3,4,5]
,现在要对lis
做累加求和运算,可以使用reduce
:
1 | from functools import reduce |
在上面的代码中,我们定义了一个函数add
,用于求解两数之和。
而reduce
函数内部所做事情如下:
1 | 第一步,将lis中的1和2传入函数add,得到返回值3 |
匿名函数
匿名函数可以将一些逻辑比较简单的函数写成一句代码,从而使得代码变得更加简洁。
前面我们定义的函数都是使用的关键字def
,后接函数名,而匿名函数就是指那些没有名字的函数,既然没有名字,那么就不能使用def
来定义了。
匿名函数使用lambda
来定义,具体格式如下:
其中的参数可以有多个。
举个例子,求解一个数的立方
如果使用def
,可以这样写:
1 | def cube(x): |
调用时是这样子的:
1 | 3)#求3的立方 cube( |
现在,要求你使用匿名函数,则定义函数只需一行代码:
1 | lambda x:x**3 |
其中,:
前面的x
是参数,:
后面的x
是表达式,相当于def
定义的函数体内的语句。
那如何调用匿名函数呢?
可以这样:
1 | lambda x:x**3)(3) ( |
额,可能看起来有点奇怪,来解释下:
lambda x:x**3
这一个整体其实就是一个函数,我们可以用type()
来验证这一点:<br。
1 | type(lambda x:x**3) |
既然是函数,那么就可以和正常函数一样调用,唯一的区别就是匿名函数没有名字而已。正因为没有名字,所以在调用时要用()
将整个函数包裹起来,以表明这是一个整体。我们已经知道了这个整体是个函数,于是在这个整体的后面写一对()
,()
内部传入参数,就完成了匿名函数的调用过程。
在上面的例子中,我们传入的参数就是数字3
,调用该匿名函数就可以求解3
的立方了。
其实,函数也可以赋值给一个变量。知道这一点的话,我们就可以给匿名函数起名字了。
接着看上面的栗子,我们将lambda x:x**3
这个函数赋值给变量f
,即:
1 | f=lambda x:x**3 |
这样,我们就完成了给匿名函数起名字的操作,这里,函数的名字就叫做f
。
现在,你就可以像调用def
定义的函数那样对函数lambda x:x**3
进行调用了:
1 | 3)#求3的立方 f( |
说了这么多,你可能会问:这跟今天要学习的高阶函数有什么关系?
没关系!谢谢~
好吧,其实一开始就说了,匿名函数可以将逻辑比较简单的函数写成一句代码,从而使得代码更加简洁。
我们可以将最开始求平方的那个栗子用匿名函数改写
先回顾一下原代码:
1 | lis=[1,2,3] |
接着来使用匿名函数:
1 | lis=[1,2,3] |
但这不是最正宗的,因为匿名函数嘛,要匿名啊!这里还给匿名函数起了个名字叫f
,最后才传入了map
。
于是最终的代码如下:
1 | lis=[1,2,3] |
以后,在使用高阶函数时,你将经常看到如上代码所示形式的匿名函数。