概念:
函数是python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解程简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
而函数式编程(请注意多了一个”式“字)--Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。
我们首先要搞明白计算机(Computer)和计算(Compute)的概念。
在计算机的层次上,CPU执行的式加减乘除的指令代码,以及各种条件判断和跳转指令,所以汇编语言是最贴近计算机的语言。
而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。
对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!1
Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。
高阶函数(Higher-order function):
高阶函数英文叫Higher-order function。什么是高阶函数?我们以实际代码为例子,一步一步深入概念。
变量可以指向函数
以Python内置的求绝对值的函数abs()为例,调用函数可以使用
abs(-10)
如果只写abs呢?
abs
可见,abs(-10)是函数调用,而abs是函数本身。
要获得函数调用结果,我们可以把结果赋值给变量:
x = abs(-10)
如果把函数本身赋值给变量呢?
f = abs
结论:函数本身也可以赋值给变量,即:变量可以指向函数
如果一个变量指向了一个函数,那么,可否通过该变量来调用这个函数?
答案是可以的,说明变量f可以指向abs函数本身。直接调用abs()函数和调用变量f()完全相同。
回归到标题:变量可以指向函数
函数名也是变量
那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
abs = 10
print(abs(-10))
Traceback (most recent call last):
File "e:\py\Higher-order function.py", line 9, in <module>
print(abs(-10))
^^^^^^^^
TypeError: 'int' object is not callable
把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10。
当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。
这里也就回归到标题:函数名也是变量。
传入函数
既然变量可以指向函数,函数的参量能接受变量,那么一个函数就可以接受另一个函数作为参数,这种函数就称之为高阶函数(high-order function)。
一个最简单的高阶函数:
def add(x,y,f)
return f(x)+f(y)
当我们调用add(-5,6,abs)时,参数x,y和f分别接收-5,6和abs,根据函数定义,我们可以推导计算过程为:
用代码验证:
print(add(-5,6,abs))
标题传入函数的意思就是把一个函数作为参数传入到一个函数中,也即高阶函数。
map/reduce
python内建了map()和reduce()函数
此处,廖雪峰老师给了一篇引文:MapReduce:大规模集群上简化数据处理(Simplified Data Processing on Large Clusters)
map:map()函数接收两个参数,一个是函数,一个是Iterable(这个前文中有提到,即可用于for循环的一系列数据类型),map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
举例说明,比如我们有一个函数f(x)=x^2,要把这个函数作用在一个list[1,2,3,4,5,6,7,8,9]上,就可以用map()实现如下:
用python代码实现:
def f(x):
return x*x
r = map(f,[1,2,3,4,5,6,7,8,9])
print(list(r))
map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列
(注:惰性序列(Lazy Sequence):也称为延迟求值序列,是一种在需要时才计算其元素的序列。与普通序列(如列表)不同,惰性序列不会一次性将所有元素都计算出来,而是等到你真正需要某个元素时,才会去计算它。
特点:
- 延迟计算:只有在真正需要时才计算元素的值。
- 无限序列:可以表示无限长的序列,因为不需要一次性存储所有元素。
- 内存效率:对于大型数据集,惰性序列可以显著减少内存占用。
- 灵活组合:惰性序列可以方便地与其他操作组合,如过滤、映射等。
应用场景:
- 数据流处理:处理大规模数据集时,惰性序列可以逐个处理元素,避免内存溢出、
- 函数式编程:惰性序列是函数式编程中的重要概念,可以实现许多强大的编程模式
- 生成器(generator):Python中的生成器就是一种创建惰性序列的常见方式
- 无限序列:惰性序列可以表示无限序列,如自然数序列,斐波那契数列等)
因此通过list()函数让它把整个序列都计算出来并返回一个list。
你可能会想,不需要map()函数,写一个循环,也可以计算出结果:
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)=x^2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:
print(list(map(str,[1,2,3,4,5,6,7,8,9])))
只需要一行代码
reduce:reduce把一个函数作用在一个序列[x1,x2,x3,...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f,[x1,x2,x3,x4]) = f(f(f(x1,x2),x3),x4)
比方说对一个序列求和,就可以用reduce实现(但需要注意的是reduce函数在python2中是内置函数,但在python3中非内置函数)
from functools import reduce
def add(x,y):
return x+y
print(reduce(add,[1,3,5,7,9]))
把序列[1,3,5,7,9]变换为整数13579,reduce就可以派上用场:
from functools import reduce
def fn(x,y):
return x * 10 + y
print(reduce(fn,[1,3,5,7,9]))
这个例子本身没多大用处,但是,如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换为int的函数:
from functools import reduce
def fn(x,y):
return x*10 + y
def char2num(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]
print(reduce(fn,map(char2num,'24678')))
整理成一个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 char2num(s):
return DIGITS[s]
return reduce(fn,map(char2num,s))
print(str2int('12345'))
还可以用lambda函数进一步简化成:
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(char2num,s))
print(str2int('56789'))
(注:lambda函数是一种匿名函数,它可以用一行代码定义一个简单的函数。它的语法非常简洁,通常用于那些不需要重复使用的函数
基本语法:lambda 参数列表:表达式
例子:add = lambda x,y:x+y print(add(3,4)) #7
优点:
- 简洁:代码更紧凑,易于阅读
- 灵活:可以作为参数传递给其他函数,或者嵌入到更大的表达式中
- 一次性使用:对于简单的函数,lambda函数可以避免定义一个完整的函数)
回归标题:其实map()和reduce()函数的主要区别就是:map()是对序列依次运算,将一个函数作用于一个序列,以此得到另一个序列;reduce则是累积运算,用于将一个函数依次作用于上次计算的结果和序列的下一个元素,以此得到最终结果。
练习:
利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入['adam','LISA','barT'],输出['Adam','Lisa','Bart']
def normalize(name):
return name[0].upper() + name[1:].lower()
# 测试:
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)
或者用name.title,因为在python中不存在title函数,其是一个字符串对象的方法,作用是:将字符串中的每个单词的首字母大写,其余字母小写。
Python提供的sum()函数可以接收一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:
from functools import reduce
def prod(L):
def fn(x,y):
return x*y
return reduce(fn,L)
print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
if prod([3, 5, 7, 9]) == 945:
print('测试成功!')
else:
print('测试失败!')
利用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} #首先定义一个"number"对应对应数字的字典,"."对应-1
def str2float(s):
nums = map(lambda ch: CHAR_TO_FLOAT[ch], s) #用map和lambda把字符s转换为字典
point = 0 #用于初始化小数点的位置,这里表示还没遇到小数点
def to_float(f, n):
nonlocal point #用于声明非全局变量,即在嵌套函数中修改上一层函数的局部变量
if n == -1: #如果n=-1,表示遇到了小数点,将point设置为1,表示进入小数部分
point = 1
return f
if point == 0: #如果没遇到小数点(point为0),则把之前的f乘以10再加上当前数字n
return f * 10 + n
else: #如果point不为0,说明当前数字是小数部分,将point乘以10,将n除以point得到小数部分的值,然后加到f上。
point = point * 10
return f + n / point
return reduce(to_float, nums, 0.0) #初始值为0.0
print('str2float(\'123.456\') =', str2float('123.456'))
if abs(str2float('123.456') - 123.456) < 0.00001:
print('测试成功!')
else:
print('测试失败!')