python3 第二十章 - 函数式编程之Higher-order function(高阶函数)
什么是高阶函数?把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
在前面的章节中,我们知道可以用abs()这个函数来得到一个数的绝对值,如:
print('abs(-100):', abs(-100))
以上代码,输出:
abs(-100): 100
如果,我们把代码修改下,把abs赋值给一个变量:
f = abs print(f(-100))
以上代码,输出:
100
可见,abs(-100)是函数调用,而abs是函数本身,要获得函数调用结果,我们可以把结果赋值给变量。函数本身也可以赋值给变量,即:变量可以指向函数,这时变量就获得了函数的功能,如上例中的 f ,直接调用f()和直接调用abs()完全相同。
那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
如果把abs指向其他对象,会有什么情况发生?
abs = 10
print(abs(-10))
把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10!当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。
注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10。关于什么是模块,后面会讲到,这里不要去纠结。
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
一个最简单的高阶函数:
def add(x, y, f): return f(x) + f(y)
当我们调用add(-5, 6, abs)时,参数x,y和f分别接收-5,6和abs,根据函数定义,我们可以推导计算过程为:
x = -5 y = 6 f = abs f(x) + f(y) ==> abs(-5) + abs(6) ==> 11 return 11
用代码验证下:
def add(x, y, f): return f(x) + f(y) print(add(-5, 6, abs))
1、map函数
这是Python内置的一个函数,map()函数接收两个参数,一个是函数,一个是Iterable(迭代器对象),map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator(迭代器)返回:
def f(x): """ 返回一个数的3次方 :param x: 数 :return: 3次方后的结果 """ return x**3 r = map(f, [1, 2, 3, 4, 5]) print(list(r))
以上代码,输出:
[1, 8, 27, 64, 125]
map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
当然,不需要map()函数,写一个循环,也可以计算出结果:
def f(x): """ 返回一个数的3次方 :param x: 数 :return: 3次方后的结果 """ return x**3 L = [] for n in [1, 2, 3, 4, 5]: L.append(f(n)) print(L)
执行后,结果是一样的,但这样的写法,我们并不能直观的知道f()是作用在列表的所有元素上,并返回一个列表,我们必须读了源代码后才知道。
所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x3,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:
print(list(map(str, [1, 2, 3, 4, 5])))
以上代码,输出:
['1', '2', '3', '4', '5']
当然,更多的时候我们也会配合lambda来使用,如:
r = map(lambda x: x**3, [1, 2, 3, 4, 5]) print(list(r))
以上代码,输出:
[1, 8, 27, 64, 125]
2、reduce函数
reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
对一个序列求和,就可以用reduce实现:
from functools import reduce def add(x, y): return x + y print('reduce(add, [1, 3, 5, 7, 9]) = ', reduce(add, [1, 3, 5, 7, 9]))
以上代码,输出:
reduce(add, [1, 3, 5, 7, 9]) = 25
当然求和运算可以直接用Python内建函数sum(),思考:如何把序列 [1, 2, 3, 4, 5] 转化为整型的 12345 ,下面给出代码但请尽可能自己先写出
from functools import reduce def f(x, y): return x*10 + y res = reduce(f, [1, 2, 3, 4, 5]) print(res)
思考:假如python没有提供 int() 函数,如何使用 map与reduce自己写一个函数,实现将 '123456' 转化为 123456 ,下面给出代码但请尽可能自己先写出:
from functools import reduce def str2int(s): """ 将字符串转为数字 :param s: 要转化的字符串 :return: 数字 """ DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} def char2num(c): """ 字符转数字 :param c:字符 :return: 数字 """ return DIGITS[c] def f(x, y): return x*10 + y resList = map(char2num, s) # 将字符串转为列表 return reduce(f, resList) # 将列表元素依次排列组成数字,并返回 print(str2int('1234567'))
3、filter函数
Python内建的filter()函数用于过滤序列。filter()接收一个函数和一个序列,把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
def is_odd(n): return n % 2 == 1 res = list(filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8])) print(res)
以上代码,输出:
[1, 3, 5, 7]
注:filter()函数返回的是一个Iterator,也就是一个惰性序列,filter()的作用是从一个序列中筛出符合条件的元素。由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。
4、sorted函数
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。
Python内置的sorted()函数就可以对list进行排序:
print(sorted([36, 5, -12, 9, -21]))
以上代码,输出:
[-21, -12, 5, 9, 36]
它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:
print(sorted([36, 5, -12, 9, -21], key=abs))
以上代码,输出:
[5, 9, -12, -21, 36]
key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
这个是数字,那字符串排序是怎么样的呢?我们来看实例:
print(sorted(['hello', 'world', 'roy', 'python', 'c++']))
以上代码,输出:
['c++', 'hello', 'python', 'roy', 'world']
默认情况下,对字符串排序,是按照ASCII的大小比较的,每个字符依次比较(两个字符串比较,先比较第一个字符,当第一个字符分出大小时则结束比较且比较的结果即为第一个字符相比较的结果,当第一个字符相同时则比较第二个字符,第二个字符相同时则比较第三个,依次下去直到得出结果),由于'Z' < 'a',结果,大写字母Z会排在小写字母a的前面。
可以用 reverse=True 进行反向排序:
print(sorted([36, 5, -12, 9, -21], reverse=True)) print(sorted(['hello', 'world', 'roy', 'python', 'c++'], reverse=True))
以上代码,输出:
[36, 9, 5, -12, -21] ['world', 'roy', 'python', 'hello', 'c++']
下一篇: 一段有意思的fork()程序