函数式编程
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
而函数式编程–Functional Programming,虽然也可以归结到面向过程的程序设计,但是其思想更接近数学计算。
我们首先要搞明白计算机(Computer)和计算(Compute)的概念。
在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。
而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。
对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
高阶函数
高阶函数英文叫Higher-order function。什么是高阶函数?我们以实际代码为例子,一步一步深入概念。
1 变量可以指向函数
以Python内置的求绝对值的函数abs()为例,调用该函数用以下代码:
但是,如果只写abs呢?
可见,abs(-10)是函数调用,而abs是函数本身。
(built-in function abs 内置函数abs)
要获得函数调用结果,我么可以把结果赋值给变量:
但是,如果把函数本身赋值给变量呢?
结论:
函数本身也可以赋值给变量,即:变量可以指向函数。
如果一个变量指向了一个函数,那么,可否通过该变量来调用这个函数呢?我们用代码验证一下:
成功!说明变量f现在已经指向了abs函数本身。直接调用abs()函数和调用变量f()完全相同。
2 函数名也是变量
那么函数名是什么呢?函数名其实就是指向函数的变量!
对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
如果把abs指向其他对象,会有什么情况发生呢?
把abs指向10后,就无法通过abs(-10)调用该函数了!
因为abs这个变量已经不指向求绝对值函数而是指向一个整数10了!
当然,我们实际当中不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,就只能重启Python交互式环境了。
注意:由于abs函数实际上是定义在(builtin) 模块中的,所以要让修改abs变量的指向在其他模块也生效,要用(_builtin_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
用代码验证一下:
编写高阶函数,就是让函数的参数能够就受到别的函数。
小结:
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
高阶函数-map/reduce
Python内建了map()和reduce()函数。
如果想要了解map/reduce概念的,可以阅读Google的论文:
《MapReduce: Simplified Data Processing on Large Clusters》
我们先来说map。
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
举例说明,比如我们有一个函数f(x) = x*x,要把这个函数作用在一个list [1,2,3,4,5,6,7,8,9]上,就可以用map()实现如下:
(摘 廖雪峰老师Python教程)
现在,我们用Python代码实现:
map()传入的第一个参数是f,即函数对象本身。
由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
你可能会想,不需要map()函数,写一个循环,也可以计算出结果:
的确可以,但是,从上面的循环代码,能一眼看明白“把f(x)作用在list的每一个元素并把结果生成一个新的list”吗?
所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x*x,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:
只需要一行代码就欧拉。
再看reduce()函数的用法。reduce把一个函数作用在一个序列[x1,x2,x3,…]上,这个函数必须接受两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f,[x1,x2,x3,x4])=f(f(f(x1,x2),x3),x4)
比如说对一个序列求和,就可以用reduce实现:
当然,求和运算可以直接使用Python内建函数sum(),没必要动用reduce()。
但是如果要把序列[1,3,5,7,9]变成整数13579,reduce就可以派上用场了:
这个例子本身没有多大用处,但是,如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换为int的函数:
整理成一个str2int的函数就是:
还可以用lambda函数进一步简化:
也就是说,假设Python没有提供int()函数,你完全可以自己写一个把字符串转化为整数的函数,而且只需要几行代码!
lambad函数的用法以后在介绍。
小练习:
利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:[‘adam’,‘LISA’,‘barT’],输出[‘Adam’,’Lisa’,’Bart’]:
def normalize(name):
return name.title()
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)
Python提供的sum()函数可以接受一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:
from functools import reduce
def prod(L):
def mult(x, y):
return x * y
return reduce(mult, L)
print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
利用map和reduce编写一个str2float函数,把字符串’123.456’转换成浮点数123.456:
from functools import reduce
CHAR_TO_FLOAT = {
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'.': -1
}
def str2float(s):
nums = map(lambda ch:CHAR_TO_FLOAT[ch], s)
point = 0
def to_float(f, n):
nonlocal point
if n == -1:
point = 1
return f
if point == 0:
return f * 10 + n
else:
point = point * 10
return f + n/point
return reduce(to_float,nums,0.0)
print(str2float('0'))
print(str2float('123.456'))
print(str2float('123.45600'))
print(str2float('0.1234'))
print(str2float('.1234'))
print(str2float('120.0034'))
from functools import reduce
def str2float(s):
float = len(s) - s.index('.') - 1
s = s.replace('.', '')
def chr2num(m):
return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[m]
def order(a, b):
return a * 10 + b
return reduce(order, map(chr2num, s)) / 10 ** float
print('str2float(\'123.456\')=', str2float('123.456'))
高阶函数-filter
Python内建的filter()函数用于过滤序列。
和map()类似,filter()也接收一个函数和一个序列。
和map()不同的是,filter()把传入的函数依次作用于每一个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
def is_odd(n):
return n % 2 == 1
print(list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])))
把一个序列中的空字符串删掉,可以这么写:
def not_empty(s):
return s and s.strip()
print(list(filter(not_empty, ['A', '', 'B', None, 'C', ''])))
注意strip函数
def strip(self, chars=None):
"""Return a copy of the string with the leading and trailing
characters removed.
:type chars: bytes | None
:rtype: bytes
"""
return b''
可见用filter()这个高阶函数关键在于正确实现一个“筛选”函数。
注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。
用filter求素数
计算素数的一个方法是埃式筛法,它的算法理解起来非常简单:
首先,列出从2开始的所有自然数,构造一个序列:
2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,…取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:
3,5,7,9,11,13,15,17,19,…
取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:
5,7,11,13,17,19,…
取新序列的第一个数5,然后用5把序列的5的倍数筛掉:
7,11,13,17,19,…
不断筛下去,就可以得到所有的素数。
用Python来实现这个算法,可以先构造一个从3开始的奇数序列:
def _odd_iter():
n = 1
while True:
n = n + 2
yield n
上面这是一个生成器,并且是一个无限序列。
def _not_divisible(n):
return lambda x: x % n > 0
上面定义了一个筛选函数
def primes():
yield 2
it = _odd_iter() #初始序列
while True:
n = next(it) #返回序列的第一个数
yield n
it = filter(_not_divisible(n), it)
最后,定义了一个生成器,不断返回下一个素数
这个生成器先返回第一个素数2,然后,利用filter()不断产生筛选后的新的序列。
由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件:
# 打印1000以内的素数
for n in primes():
if n < 1000:
print(n)
else:
break
注意到Iterator是惰性计算的序列,所以我们可以用Python表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。
C:\SoftWare\Python3.5.1\python.exe E:/pythonProjects/python3Learning/filter/_odd_iter.py
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
101
103
107
109
113
127
131
137
139
149
151
157
163
167
173
179
181
191
193
197
199
211
223
227
229
233
239
241
251
257
263
269
271
277
281
283
293
307
311
313
317
331
337
347
349
353
359
367
373
379
383
389
397
401
409
419
421
431
433
439
443
449
457
461
463
467
479
487
491
499
503
509
521
523
541
547
557
563
569
571
577
587
593
599
601
607
613
617
619
631
641
643
647
653
659
661
673
677
683
691
701
709
719
727
733
739
743
751
757
761
769
773
787
797
809
811
821
823
827
829
839
853
857
859
863
877
881
883
887
907
911
919
929
937
941
947
953
967
971
977
983
991
997
Process finished with exit code 0
小练习:
回数是指从左向右读和从右向左读都是一样的数,例如12321,909.请利用filter()滤掉非回数。
高阶函数-sorted
排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。
Python内置的sorted()函数就可以对list进行排序:
此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按照绝对值大小排序:
key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。对比原始的list和经过key=abs处理过的list:
list = [36, 5, -12, 9, -21]
keys = [36, 5, 12, 9, 21]
然后sorted()函数按照keys进行排序,并按照对应关系返回list相应的元素:
keys排序结果 => [5, 9, 12, 21, 36]
最终结果 => [5, 9, -12, -21, 36]
我们再看一个字符串排序的例子:
默认情况下,对字符串排序,是按照ASCII的大小比较的,由于‘Z’<’a’,结果,大写字母Z会排在小写字母a的前面。
现在,我们提出排序应该忽略大小写按照字母序排序。
要实现这个算法,不必对现有代码大加改动,只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),在比较。
这样,我们给sorted()传入key函数,即可实现忽略大小写的排序:
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True:
从上例可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持的非常简洁。
注意:sorted()函数
def sorted(*args, **kwargs): # real signature unknown
"""
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customise the sort order, and the
reverse flag can be set to request the result in descending order.
"""
pass
小结
sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数。
小练习:
假设我们用一组tuple表示学生名字和成绩:
L = [('Bob', 75),('Adam', 92),('Bart', 66),('Lisa', 88)]
使用sorted()对上述列表分别按名字排序:
from operator import itemgetter
students = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
print(sorted(students, key=itemgetter(0)))
print(sorted(students, key=lambda t: t[1]))
print(sorted(students, key=itemgetter(1), reversed=True))