函数式编程
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。
了解一下计算机(Computer)和计算(Compute)的概念。在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。
对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
高阶函数
高阶函数英文叫Higher-order function。
变量可以指向函数
以Python内置的求绝对值的函数abs()为例,调用该函数用abs(-1),但是如果只写abs:
>>> abs
<built-in function abs>
可见,abs(-10)是函数调用,而abs是函数本身。
要获得函数调用结果,我们可以把结果赋值给变量:
>>> x = abs(-10)
>>> x
10
如果把函数本身赋值给变量:
>>> f = abs
>>> f
<built-in function abs>
结论:函数本身也可以赋值给变量,即:变量可以指向函数。
如果一个变量指向了一个函数,那么,可否通过该变量来调用这个函数?
>>> f = abs
>>> f(-10)
10
说明变量f现在已经指向了abs函数本身。直接调用abs()函数和调用变量f()完全相同。
函数名也是变量
函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!如果把abs指向其他对象呢?
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10!
当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。
注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10。
传入函数
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
一个最简单的高阶函数:
>>> def add(x,y,f):
... return f(x)+f(y)
...
>>> print(add(-5,6,abs))
11
当我们调用add(-5, 6, abs)时,参数x,y和f分别接收-5,6和abs。
编写高阶函数,就是让函数的参数能够接收别的函数。
小结
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
map/reduce
Python内建了map()和reduce()函数。
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
有一个函数f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下:
>>> def f(x):
... return x*x
...
>>> r=map(f,[1,2,3,4,5,6,7,8,9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
写一个循环,也可以计算出结果:
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
L.append(f(n))
print(L)
但是,从上面的循环代码,能一眼看明白“把f(x)作用在list的每一个元素并把结果生成一个新的list”吗?
所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,效果就是:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
一个序列求和,就可以用reduce实现:
>>> def add(x,y):
... return x+y
...
>>> reduce(add,[1,2,3,4,5,6])
21
当然求和运算可以直接用Python内建函数sum(),没必要动用reduce。
但是如果要把序列[1, 2, 3, 4]变换成整数1234,reduce就可以派上用场:
>>> from functools import reduce
>>> def f(x,y):
... return x*10+y
...
>>> reduce(f,[1,2,3,4])
1234
这个例子本身没多大用处,但是,如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换为int的函数:
>>> from functools import reduce
>>> def fn(x,y):
... return x*10+y
...
>>> def char2int(s):
... digits={'0':0,'1':1,'2':2,'3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
... return digits[s]
...
>>> reduce(fn,map(char2int,'123456'))
123456
整理成一个str2int的函数就是:
>>> from functools import reduce
>>> digits={'0':0,'1':1,'2':2,'3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
>>> def str2int(s):
... def fn(x,y):
... return x*10+y
... def char2int(s):
... return digits[s]
... return reduce(fn,map(char2int,s))
...
>>> str2int('123')
123
还可以用lambda函数进一步简化(lambda 是python定义匿名函数的语法,lambda x:x*x,这个匿名函数的形参是x,表达式x *x 的值就是这个函数的返回值。)
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2int(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2int, s))
也就是说,假设Python没有提供int()函数,你完全可以自己写一个把字符串转化为整数的函数,而且只需要几行代码!
练习
利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:[‘adam’, ‘LISA’, ‘barT’],输出:[‘Adam’, ‘Lisa’, ‘Bart’]:
>>> list(map(normalize,['adam','LISA','barT']))
['A', 'L', 'B']
>>> def normalize(name):
... name=name[0].upper()+name[1:].lower()
... return name
...
>>> list(map(normalize,['adam','LISA','barT']))
['Adam', 'Lisa', 'Bart']
Python提供的sum()函数可以接受一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:
>>> from functools import reduce
>>> def prod(x,y):
... return x*y
...
>>> reduce(prod,[1,2,3,4,5,6,7,8,9])
362880
利用map和reduce编写一个str2float函数,把字符串’123.4567’转换成浮点数123.4567:
from functools import reduce
def str2float(s):
def fn(x,y):
return x*10+y
n=s.index('.')
s1=list(map(int,[x for x in s[:n]]))
s2=list(map(int,[x for x in s[n+1:]]))
return reduce(fn,s1) + reduce(fn,s2)/10**len(s2)
print('\'123.4567\'=',str2float('123.4567'))
if abs(str2float('123.4567') - 123.4567) < 0.00001:
print('测试成功!')
else:
print('测试失败!')
F:\>python hello.py
'123.4567'= 123.4567
测试成功!
Python index() 方法检测字符串中是否包含子字符串 str ,如果指定 beg(开始) 和 end(结束) 范围,则检查是否包含在指定范围内,该方法与 python find()方法一样,只不过如果str不在 string中会报一个异常。
index()方法语法:str.index(str, beg=0, end=len(string))
str – 指定检索的字符串beg – 开始索引,默认为0。end – 结束索引,默认为字符串的长度。如果包含子字符串返回开始的索引值,否则抛出异常。
注意:
1行,从 functools 包里调用 reduce
3-4行,定义一个 fn() 函数,用来把S1,S2这两个list里面的元素变成一个数。
5行,很关键的一步,利用 index() 函数确定字符串 S 中 ‘.’的位置。
6-7行,先利用切片把我们传入的 str 分成以前以后两个部分(其实就是根据小数点分成整数和浮点数,分别处理),然后再把切割好的 str 利用 int 变成整数,map() 函数负责把 int 作用到截取的 str 的每个元素中去。
8行,就说一个知识点,其他的都很好理解,m**n 这个表达的就是 m 的 n 次方。