一、函数式编程
函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。而函数式编程(-- Functional Programming),虽然也可以归纳到面向过程的程序设计,但其思想更接近”数学计算“。
首先让我们明了计算机(Computer)与计算(Compute)的概念。在计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。而计算则是指数学意义上的计算,越是抽象的计算,离计算机硬件越远。对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
二、高阶函数
高阶函数英文叫Higher-order function。什么是高阶函数?我们以实际代码为例子,一步一步深入概念
首先,变量也可以指向函数!
以Python内置的求绝对值的函数abs()为例,调用该函数用以下代码:
abs(-10)
但是,如果只写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)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10!当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。
注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
def add(x, y, f):
return f(x) + f(y)
res = add(-1, -19, abs)
print(res)
这是一个最简单的高阶函数
所以编写高阶函数,就是让函数的参数能够接收别的函数。
小结:
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
二、map与reduce
Python内建了map()和reduce()函数。
(一)
我们先看map。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,3,5,7,9])
print(list(r))
map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
而map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:
(二)
再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
让我们来看几个例子:
from functools import reduce
#累加得到1~9的奇数之和
def add(x,y):
return x + y
num = reduce(add,[1,3,5,7,9])
print(num)
#计算5!
def prod(x,y):
return x * y
res = reduce(prod, [1,2,3,4,5])
print(res)
#将列表[0~9]转换为int:1234567890
def fn(x,y):
return 10*x + y
real = reduce(fn, [1,2,3,4,5,6,7,8,9,0])
print(real)
#这个例子本身没多大用处,但是,如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换为int的函数
#将字符串转换为int放入列表中
def char2num(x):
#通过字典中键值对的方式进行数据类型更改
digits = {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
return digits[x]
it = reduce(fn, map(char2num, ['1','2','3','4','5','6','7','8','9','0']))
print(it)
三、实验:
str 转 float,即将一个例如‘1234.56789’的字符串转换为float。
自己写函数,不使用python自带函数或方法。
#str 转 float
from functools import reduce
def str2float(x,y):
#根据分析,点号前面的数值是x*10+y进行累加的,点号后则是x+y*10^n(n是y的位数)累加的,而当搜索到点号时,则返回x本身的规律,所以有了以下判断方式
if L.index(y) < L.index('.'):
return x*10 + y
if L.index(y) == L.index('.'):
return x
if L.index(y) > L.index('.'):
return x + y*(0.1**(L.index(y)-L.index('.')))
def char2num(x):
#因为是浮点数,所以多了点号在字典中
digits = {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'.':'.'}
return digits[x]
L = list(map(char2num, list('3.456')))
print(L)
result = reduce(str2float, L)
print(result)
#问题,为什么当输入3.456时,输出是3.4559999999999995???