参考教程:廖雪峰官网https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
函数式编程
一、高阶函数
所谓高阶函数就是函数可以以另外一个函数作为参数。
首先需要了解在Python中变量是可以指向函数的,如下例子:
>>> abs(-5)
5
>>> f=abs
>>> f
<built-in function abs>
>>> abs
<built-in function abs>
>>> f(-5)
5
可以看到当变量f指向函数名abs后,f也成为了一个求绝对值的函数名,并且可以通过f调用。
函数名的本质也是变量,只是在Python中直接设置了该变量就指向函数。
>>> abs='abc'
>>> abs
'abc'
>>> abs(-1)
Traceback (most recent call last):
File "<pyshell#7>", line 1, in <module>
abs(-1)
TypeError: 'str' object is not callable
上例中把内置函数名abs指向了一个字符串,那它的本质就变化了,无法再调用其计算绝对值的方法了。(只有在下一次启动Python运行环境后才重置)。
看一个最简单的高阶函数(即以函数名作为参数):
>>> def funa(a):
return a*a
>>> def funb(a,b,f):
return f(a)+f(b)
>>> funb(3,4,funa)
25
>>> funb(-6,-10,abs)
16
(一)map/reduce
1、map
map函数接受两个参数,一个是函数,一个是Iterable(可迭代对象)。map将传入的函数依次作用到序列的每个元素,并把结果作为一个新的Iterator(迭代器)返回。
例如,用Python实现一个函数f(x)=x^2,作用到一个列表上。
>>> def fx(n):
return n**2
>>> map(fx,[1,2,3,4,5,6,7,8,9])
<map object at 0x0000000002DA8D30>
注意这里结果是一个map对象,实质上是一个迭代器(Iterator),从前面章节了解到迭代器都是惰性的,要把结果转为Iterable来输出。
>>> list(map(fx,[1,2,3,4,5,6,7,8,9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> from collections import Iterator
>>> x=map(fx,[1,2,3,4,5,6,7,8,9])
>>> isinstance(x,Iterator)
True
虽然通过其他的方法也可以实现上述生成一个结果列表的效果,但map作为一个高阶函数,过程中把运算规则抽象化处理,在应用中可以处理更复杂的函数,比如下例把一个list所有数字转为字符串只需要一行代码:
>>> list(map(str,[1,2,3,4,5]))
['1', '2', '3', '4', '5']
2、reduce
reduce同样把一个函数作用在一个序列上,但其内部的过程却和map不同,reduce是把函数的结果继续和序列的下一个元素继续计算,同时这个函数必须接受两个参数,所以效果就是:
reduce(f,[a,b,c,d])=f(f(f(a,b)+c)+d)
一个简单的求和示例:
>>> from functools import reduce
>>> def myadd(a,b):
return a+b
>>> reduce(myadd,list(range(1,101)))
5050
另外一个示例,把一个数字序列变成一个数:
>>> def myadd(a,b):
return a*10+b
>>> reduce(myadd,[1,3,7,9])
1379
配合使用map和reduce可以把一个数字字符串转换为数值:
>>> def funa(nstr):
return int(nstr)
>>> reduce(myadd,map(funa,'12345'))
12345
练习:
'''
利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。
输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']
'''
def fun1(stra):
return stra.capitalize()
name_list=['adam', 'LISA', 'barT']
print(list(map(fun1,name_list)))
'''
Python提供的sum()函数可以接受一个list并求和,
请编写一个prod()函数,可以接受一个list并利用reduce()求积:
'''
def fun2(a,b):
return a*b
def prod(lista):
return reduce(fun2,lista)
print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
if prod([3, 5, 7, 9]) == 945:
print('测试成功!')
else:
print('测试失败!')
from functools import reduce
'''
利用map和reduce编写一个str2float函数,把字符串'123.456'转换成浮点数123.456
'''
#思路:先把原始字符串分成两部分:整数和小数部分分别处理
#处理整数部分的转换
#1、先把字符转换成数值
def strTonumbera(char):
mydic={'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'5':5,'6':6,'7':7,'8':8,'9':9}
return mydic[char]
#2、定义整数部分reduce的函数参数
def myfunca(a,b):
return a*10+b
#3、通过reduce/map将整数字符串转换为整数数值
def funca(strx):
return reduce(myfunca,list(map(strTonumbera,list(strx))))
#处理小数部分的转换
#1、定义小数部分reduce的函数参数
def myfuncb(a,b):
return a/10+b
#2、通过reduce/map将整数字符串转换为整数数值
def funcb(strx):
#小数部分的处理与整数相比有个难点就是reduce推算中的处理,这里可以先把序列倒序
stry=list(strx)
stry.reverse()
print(stry)
x=reduce(myfuncb,list(map(strTonumbera,stry)))
#这时候还不是最终得数,需要再除以10
return x/10
def str2float(strx):
#整数部分
X=funca(strx.split('.')[0])
#小数部分
Y=funcb(strx.split('.')[1])
return X+Y
print(str2float('123.589'))
print('str2float(\'123.456\') =', str2float('123.456'))
if abs(str2float('123.456') - 123.456) < 0.00001:
print('测试成功!')
else:
print('测试失败!')
(二)filter
和map()类似,filter()函数也接受一个函数和一个序列,不同的是filter()把传入函数依此作用于元素后根据返回值的真伪决定保留还是丢弃这个元素。
#过滤后保留列表中的奇数
def odd(n):
return n%2==1
x=list(filter(odd,[0,1,2,3,4,5,6,7]))
print(x)
#用filter求素数
#先定义一个从3开始的奇数序列
def _odd_iter():
n=1
while True:
n=n+2
yield n
#再定义一个筛选函数,n不能被x整除则返回True
def _not_divisible(n):
def funcdiv(x):
return not x%n==0
return funcdiv
#定义一个生成器,不断返回下一个素数
def primes():
yield 2
it=_odd_iter()
while True:
n=next(it)
yield n
myfunc=_not_divisible(n)
it=filter(myfunc,it)
#等价于下面:
#it=filter(_not_divisible(n),it)
for n in primes():
if n<50:
print(n)
练习:
'''
回数是指从左向右读和从右向左读都是一样的数,例如12321,909。
请利用filter()筛选出回数
'''
#思路:转换成列表,然后倒序判断是否相等
def is_palindrome(n):
inistr=str(n)
inilist=list(inistr)
inilist.reverse()
finalstr="".join(inilist)
if finalstr==inistr:
return True
else:
return False
output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
print('测试成功!')
else:
print('测试失败!')
(三)sorted
sorted()函数也是一个高阶函数,它接受一个关键字参数'key=x',这里x也是一个函数,将函数x作用于每个元素后按默认顺序排序(数值元素是按大小,字母按ASCII表顺序),并按排序顺序返回各个原始元素:
#按数值对应的绝对值的大小排序
ln=[10,9,-18,-2]
print(sorted(ln,key=abs))
#按字母表顺序对字符串排序
la=['Zoo','apple','boss','Cat']
#注意,这里如果不用转换小写的方法则默认是按ASCII排序
print(sorted(la,key=str.lower))
#对上述反向排序,只需要将默认参数设为True
print(sorted(la,key=str.lower,reverse=True))
练习:
#假设我们用一组tuple表示学生名字和成绩
#请用sorted()对上述列表分别按名字排序
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
#需要定义一个把元组转换成第一个元素的函数,同时这里首元素要变成小写
def by_name(t):
return t[0].lower()
L2 = sorted(L, key=by_name)
print(L2)