开篇

前面已经学习了函数以及函数的各种形式的参数。

本期要介绍的高阶函数也是函数的一种,只不过与普通的函数不同的是,高阶函数的参数可以是另外一个函数,高阶函数的返回值也可以是另外一个函数

本期你将学习到Python3个常用高阶函数:

map()filter()reduce()

你完全可以把高阶函数看做是一个正常的函数(本来就是),只不过其参数和返回值可以是函数。这样学习起来可能会更加轻松。

map

map用于对某一个可迭代对象中的每一个元素施加同一种操作

map()有两个参数,第一个参数是一个函数,第二个参数是一个可迭代对象

还是拿栗子来说明:

对于列表lis=[1,2,3],将列表中的每一个元素做平方操作,并将结果存入新的列表

如果采用普通的函数解答,你应该能写出如下的类似代码:

1
2
3
4
5
6
7
8
9
10
lis=[1,2,3]
#求平方的函数
def square(x):
return x**2
new=[]
#遍历求平方
for x in lis:
temp=square(x)
new.append(temp)
print(new)

而若使用map函数,代码是这样子的:

1
2
3
4
5
lis=[1,2,3]
def square(x):
return x**2
new=map(square,lis)
print(list(new))

看,使用了map()函数,免去了繁琐的for循环操作,代码变得非常简洁了。

你可能会说:也没什么大不了的,就多了个循环呗,写就是咯~

其实,当列表中元素个数较多时,使用map()的优势就体现出来了。

因为map()函数的返回值是一个map object(至于这是个啥,以后会讲到,这里结合下面的例子来理解即可),并不是存储实际元素的列表。当列表中元素个数较多时,若使用for循环处理该问题,则会得到一个很大的列表,即使你只是想取结果列表中的一部分元素,也会将全部元素存储起来,此时会占用很多内存,造成内存资源的浪费,而使用map则不会出现以上问题。

为了加深理解,我们来对比一下分别使用for循环和map函数所得结果占用的内存空间大小:

【补充】在Python中查看变量所占内存大小的方法

1
2
import sys
sys.getsizeof(x)

其中的x就是你的变量,sys.getsizeof(x)的返回值就是变量x所占内存空间大小。

现在,将最开始的问题中的lislis=[1,2,3]改为lis=list(range(1,10000+1)),也就是lis=[1,2,3,...,9999,10000],其余条件不变,重新作答。

[1].使用for循环的方法

1
2
3
4
5
6
7
8
9
10
11
12
import sys
lis=list(range(1,10000+1))
#求平方的函数
def square(x):
return x**2
new=[]
#遍历求平方
for x in lis:
temp=square(x)
new.append(temp)
m=sys.getsizeof(new)
print("循环方法所得结果占用内存:",m)

输出

1
循环方法所得结果占用内存: 87624

[2].使用map()函数

1
2
3
4
5
6
7
8
import sys
lis=list(range(1,10000+1))
#求平方的函数
def square(x):
return x**2
new=map(square,lis)
m=sys.getsizeof(new)
print("循环方法所得结果占用内存:",m)

输出

1
循环方法所得结果占用内存: 56

对比来看,使用map()函数所占内存更少。

如果想要取到map()的结果,可以使用list()将其返回值包裹,也就是将其返回值转为列表类型,这样就能得到和【for循环方法】中的列表new一样的结果了。

当然,你可以选择只取前n个,下面的函数就是用于选取前n个元素:

1
2
3
4
5
def get_item(n):
for it in new:
if n>0:
n=n-1
print(it,end=' ')

比如果只想获取前5个元素,就可以这样调用函数:

1
get_item(5)

得到结果

1
1 4 9 16 25 

此时,其余未被选取的元素并不会占用内存。

这样就避免了内存资源的浪费。

【温馨提示】如果你尝试打印【for循环方法】中的列表new,你的IDLE可能会卡死,因为数据量有点大。

接下来要讲的filterreduce的原理和map差不多,大家注意对照着学习。

filter

filter用于从某一个可迭代对象中筛选满足特定条件的元素

举个例子,求出120以内所有能被3整除的数字,要求使用filter

我们可以这样写:

1
2
3
4
5
6
7
lis=[i for i in range(1,20+1)]
def func(x):
if x%3==0:
return True
#求解
res=filter(func,lis)
print(list(res))

输出

1
[3, 6, 9, 12, 15, 18]

在上面的代码中,我们定义了一个函数func,用于判断一个数字是不是可以被3整除,如果能,就返回True,如果不能被3整除,此时就没有指定返回值,那么返回值就是默认值None,而None的布尔值为False,也就“相当于”在不能被3整除时返回了False

接下来使用了filter求解,filter函数的第一个参数是函数func,第二个参数是可迭代对象lisfilter(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
2
3
4
5
6
from functools import reduce
lis=[1,2,3,4,5]
def add(a,b):
return a+b
res=reduce(add,lis)
print(res)

在上面的代码中,我们定义了一个函数add,用于求解两数之和。

reduce函数内部所做事情如下:

1
2
3
4
5
6
7
第一步,将lis中的1和2传入函数add,得到返回值3

第二步,将第一步中得到的返回值3与lis中的元素3传入函数add,得到返回值6

第三步,将第二步中得到的返回值6与lis中的元素4传入函数add,得到返回值10

第四步,将第三步得到的返回值10与lis中的元素5传入函数add,得到返回值15,执行结束,最终的"累计"结果便是15

匿名函数

匿名函数可以将一些逻辑比较简单的函数写成一句代码,从而使得代码变得更加简洁。

前面我们定义的函数都是使用的关键字def,后接函数名,而匿名函数就是指那些没有名字的函数,既然没有名字,那么就不能使用def来定义了。

匿名函数使用lambda来定义,具体格式如下:

img

其中的参数可以有多个。

举个例子,求解一个数的立方

如果使用def,可以这样写:

1
2
def cube(x):
return x**3

调用时是这样子的:

1
2
>>> cube(3)#求3的立方
27

现在,要求你使用匿名函数,则定义函数只需一行代码:

1
lambda x:x**3

其中,:前面的x是参数,:后面的x是表达式,相当于def定义的函数体内的语句。

那如何调用匿名函数呢?

可以这样:

1
2
>>> (lambda x:x**3)(3)
27

额,可能看起来有点奇怪,来解释下:

lambda x:x**3这一个整体其实就是一个函数,我们可以用type()来验证这一点:<br。

1
2
>>> type(lambda x:x**3)
<class 'function'>

既然是函数,那么就可以和正常函数一样调用,唯一的区别就是匿名函数没有名字而已。正因为没有名字,所以在调用时要用()将整个函数包裹起来,以表明这是一个整体。我们已经知道了这个整体是个函数,于是在这个整体的后面写一对()()内部传入参数,就完成了匿名函数的调用过程。

在上面的例子中,我们传入的参数就是数字3,调用该匿名函数就可以求解3的立方了。

其实,函数也可以赋值给一个变量。知道这一点的话,我们就可以给匿名函数起名字了。

接着看上面的栗子,我们将lambda x:x**3这个函数赋值给变量f,即:

1
f=lambda x:x**3

这样,我们就完成了给匿名函数起名字的操作,这里,函数的名字就叫做f

现在,你就可以像调用def定义的函数那样对函数lambda x:x**3进行调用了:

1
2
3
4
>>> f(3)#求3的立方
27
>>> f(2)#求2的立方
8

说了这么多,你可能会问:这跟今天要学习的高阶函数有什么关系?

没关系!谢谢~

img

好吧,其实一开始就说了,匿名函数可以将逻辑比较简单的函数写成一句代码,从而使得代码更加简洁。

我们可以将最开始求平方的那个栗子用匿名函数改写

先回顾一下原代码:

1
2
3
4
5
lis=[1,2,3]
def square(x):
return x**2
new=map(square,lis)
print(list(new))

接着来使用匿名函数:

1
2
3
4
lis=[1,2,3]
f=lambda x:x**2
new=map(f,lis)
print(list(new))

但这不是最正宗的,因为匿名函数嘛,要匿名啊!这里还给匿名函数起了个名字叫f,最后才传入了map

于是最终的代码如下:

1
2
3
lis=[1,2,3]
new=map(lambda x:x**2,lis)
print(list(new))

以后,在使用高阶函数时,你将经常看到如上代码所示形式的匿名函数。