高阶函数:Higher-order function,把函数名作为参数传入,然后函数在函数里再次使用,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
在Python中,我们可以用一个变量来表示一个函数名,从而用这个变量去代替该函数执行其操作,比如:
这种表示法,其实是指向性的,内置的函数abs,在Python环境启动时,充其量也就是个变量,只不过,在我们没使用它之前,它的函数入口地址已经存在了内存中,而这个abs只不过是指向了这块地址,换句话说,你用另外一变量来等于这个abs,其实就已经指向了计算绝对值操作的代码块所在的内存地址,abs就相当于这块地址的门牌号,如果你弄丢了abs的门牌号,给它重新换个,会这么样呢?
显然这样的操作,会让Python解释器蒙圈掉,所以,我们在定义变量的时候,要小心,千万不要和内置的函数名发生冲突。
上面啰嗦了一两句,其目的就是想引出今天要讲的这个高阶函数:对于Python来说,变量可以接收函数名作为值去履行函数的职责,那么,函数中的参数是不是可以也可以接收函数名作为值从而在函数中去履行函数的职责呢(有点绕,其实就是,参数此时指向了一个函数的入口)。
举个例子,我们拿abs作为入口函数,我们再自定义一个add函数,函数有三个参数,前两个表示数x和y,后一个参数f我们打算让他指向abs,最后我们让add函数返回x和y的绝对值相加的结果,这个我们需要用到abs函数,但是这里,我们用f来代替之:
我们来看一下,在C++中,我们是怎么实现的:
#include "stdafx.h"
#include "math.h"
int add(int x,int y,int(*p)(int))
{
return p(x)+p(y);//这里我们只用到了abs的一个int重载
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("%d",add(-10,10,&abs));
return 0;
}
利用指针操作,我们很容易的就能实现我们上面说的结果,答案是20:
可是这样的话,看起来是不是很复杂,操作符也比较多,初学的话可能完全不理解指针是什么,没关系,我们的重点又不是指针。
接着讲,同样的例子,我们在Python中来实现一遍,复习下之前我们所学的函数的定义,我们来自己定义一个my_abs实现内置abs的功能:
#!/usr/bin/env Python3
# encoding:utf-8
#高阶函数:Higher-order function
def my_abs(x):
if not isinstance(x,(int,float)):
raise TypeError('bad operand type') #还记得这个吗?
if x>0:
return x
else:
return -x
def add(x,y,f):
return f(x)+f(y) #这里f我们给它传一个自定义的函数
print(add(-10,10,my_abs)) #这里,只需我们传入函数名即可
我们看下结果:
是不是有了点感觉,我们趁热打铁,学习下Python内置的高阶函数案列,map和reduce。
我们先讲一下map这个函数(区别于dict字典集合),首先,它肯定是接收一个函数作为参数,所以才能称之为高阶函数;下面我就看一个例子:
对于list-->[1,2,3,4,5],我们想要得到[2,4,6,8,10],我们可以采用三种方式得到:
#!/usr/bin/env Python3
# encoding:utf-8
#高阶函数:Higher-order function
#map(function,Iterable),第一个参数是一个函数,第二个参数是一个可迭代的对象
#map返回值,一个Iterator,可以通过list()函数,获得这个迭代器的整个序列
def addSelf(x):
return x+x
L=[]
#A 利用for循环调用addSelf(x)函数,从而得到目标list
for n in [1,2,3,4,5]:
L.append(addSelf(n))
print('A--------------------------')
print(L)
#B 利用生成式,得到目标list
print('B--------------------------')
print(list([x+x for x in [1,2,3,4,5]]))
#C 利用map高阶函数,转换返回的迭代器对象得到目标list
print('C--------------------------')
print(list(map(addSelf, [1,2,3,4,5])))
我们看下执行结果:
以上我们可以看出来,这个map和这个列表生成式有点像,不是有点,应该是很像,比如,生成式里面的x+x,我们如果放在一个函数里面,是不是相当于map的第一参数所指向的内容(函数),而生成式的第二个部分, for x in [....]这个是不是一个可迭代的过程,对应map的第二个参数是一个可迭代的对象,如果还不够直观明了,我们再来看一个例子:
对于list-->[1,2,3,4,5]我们想要将其元素全部转化为字符串,得到['1','2','3','4','5'],我们可以这样做:
A、生成式
B、map函数
我去,是不是很惊讶,这两种方式,一行代码就能搞定,如果是C#或者java,你想想,你能这样一行代码搞定所有元素转换吗? 这个题外话啊,我想说的是,某种程度上,这个生成式应该也算是一种高阶函数,只不过,他更加抽象,我想,它和map应该是一对失散多年的兄妹吧。
总结一下map啊,其实也不用总结了,你就想啊,生成式能干的,map也能干,只不过map有点像生成式的分解版,记住,第一个参数指向函数,第二个参数是一个可迭代的对象Iterable,比如list,tuple,dict等,且返回值是一个Iterator迭代器。
下面我们来说下另一个内置高阶函数reduce,先来看一个demo例子,对一个list进行求和计算:
#!/usr/bin/env Python3
# encoding:utf-8
#高阶函数:Higher-order function
#reduce,第一个参数指向一个函数,第二个函数是一个可迭代的对象Iterable,这个地方和map一样
'''
reduce返回值:这个区别于map,将第一个函数作用在第二参数可迭代的对象上,做累积操作得到的结果
'''
from functools import reduce #用到reduce函数,需要导入这个文件
def add(x,y):
return x+y
print('A--------------内置函数sum求和')
print(sum([1,2,3,4,5]))
print('B--------------内置高阶函数reduce求和')
print(reduce(add,[1,2,3,4,5]))
执行结果:
我们知道x+y返回的是两个数的和,而reduce函数将add函数作用在了[1,2,3,4,5]上,这时候,x+y的操作在reduce的包装下就成了:
1+2+3+4+5,每两个数相加得到的结果又可以看成新的x,而后面如果有数,就是y,这样的话,reduce执行到最后一步其实是这样的:
10+5 = x+y = 15,所以最后就得到了序列的元素和,和sum函数的效果一样。
如果我们想要得到12345这个数呢,我们利用sum肯定是做不到了,我们仍然从[1,2,3,4,5]作为出发点,我们拆分12345这个数字,发现,我们可以利用公式 x*10+y进行连续操作得到,比如123,我们可以这样做,得到12-->1*10+2 = 12,然后我们再利用公式得到123-->12*10+3=123,因此,我们可以想到,利用我刚学到的reduce函数,很容易的就能将[1,2,3,4,5]包装成12345:
#!/usr/bin/env Python3
# encoding:utf-8
#高阶函数:Higher-order function
from functools import reduce #用到reduce函数,需要导入这个文件
def fM(x,y):
return x*10+y
number = reduce(fM,[1,2,3,4,5])
print(number)
print(isinstance(number,int))#不放心,来验证下这个结果的类型
我们看下执行结果:
没毛病,我们确实得到了一个整数12345,我们想一下,如果我们的list中的元素是字符串str类型的呢,比如['1','2','3','4','5'],前面我们说过,可迭代的对象也包括str,比如我们给一个'12345',如果我们用内置的int函数,我们很容易就能得到12345,如果不用内置的int函数的话,我们可以自己写demo来实现吗?那必须啊,要是不能实现,我们今天讲的map和reduce就等于没讲,因为他俩能做的事情,生成式和内置sum也能够做到,前面说过,生成式和生成器组合天下无敌,下面,我就再来个天下无双的组合,map和reduce组合构造属于我们自己的str2int函数,不多说了,继续往下看:
A、第一步:(以下所有操作,均和int函数无关)
我们要将字符串'12345'里的每一个字符转化为一个整数,我们想想,该如何做?
(1)实现单步转换函数 char2num(s)
def char2num(s):
D = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6,
'7': 7, '8': 8, '9': 9}
return D.get(s)
这种很熟悉吧,利用字典,返回对应key的value值,好在基础数字也就10个,构造一个整数字典不是太麻烦
(2)实现一个个连续可迭代的转换 map隆重登场
print(list(map(char2num,'12345')))
看下结果:
这是什么,这是一个可迭代的序列啊,我们上面讲过,如何把一个整数序列转换为一个整数(拼接),我们用的是reduce函数,这不,reduce函数的第二个参数已经给我们准备好了,我们只差第一个参数指向函数了,我们要感谢map,借map的无私贡献,我们趁热打铁,一鼓作气,整合上面demo如下:
(写到这,我感觉眼前一片漆黑,我顺手打开了手边的灯,眼前得以恢复光明)
#!/usr/bin/env Python3
# encoding:utf-8
#高阶函数:Higher-order function
from functools import reduce #用到reduce函数,需要导入这个文件
def str2int(s):
def fM(x,y):
return x*10+y
def char2num(s):
D = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6,
'7': 7, '8': 8, '9': 9}
return D.get(s)
return reduce(fM,list(map(char2num,s)))
print((str2int('12345')))
在我们看执行结果之前,我们先来看下上面的demo,我们乍一看,感觉哪里怪怪的,怎么函数里面套函数,让人看上去有点蒙,这种写法是没问题的,我们java都学过内置类,这种写法的目的,就是占为己有,说白了,自己的东西自己用,别人要用,得经过自己的同意,同意在这里可以是函数本身,也可以用一个变量指向这个函数,为了让上面的封装更加清晰易懂,我决定,用下面的demo,为你们来呈现执行结果:
#!/usr/bin/env Python3
# encoding:utf-8
#高阶函数:Higher-order function
from functools import reduce #用到reduce函数,需要导入这个文件
def fM(x,y):
return x*10+y
def char2num(s):
D = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6,
'7': 7, '8': 8, '9': 9}
return D.get(s)
L = list(map(char2num,'12345')) #注意map返回的值是一个迭代器Iterator
print('A------------list(map)获得整数序列[1,2,3,4,5]')
print(L)
print('B------------reduce获得整数12345')
print(reduce(fM,L))
我们看下执行结果:
最后,我们再使用map实现一个小功能,学习一下Python内置函数capitalize():str对象首字母大写其余变为小写;我们试图对一个list中的name一一检查,将其更改为规范的写法,我们直接看demo:
#!/usr/bin/env Python3
# encoding:utf-8
#高阶函数:Higher-order function
def normalize(name):
return name.capitalize()
L=['kobe','AppleYk','James','aBC']
print(list(map(normalize,L)))
来来,我们看下执行的结果:
好了,本篇到这就结束了,我想,对于map和reduce两个函数,我们心里多多少少有了点底气,Python真是很有意思,这点做的真是很符合人心,我们不关心,它底层是怎么实现,只要我们能一行代码得到我们想要的,哪怕牺牲点性能,想必我们也是愿意的。
我深感没有网络是有多么的无力,但是没有乐趣,要网络又有何用,网络如果是桥梁,那么Python就是架起这座桥梁的水泥柱,二者缺一不可,乐趣自然而然产生。