开始陆续学习python相关知识,这一篇介绍函数式编程相关知识。在文章中会介绍三个主要的高阶函数,从基本例子入手了解函数式编程。
map()
map 意思是映射,在数学的学习中,映射即为x与y存在的关系,即x通过一定的方法称为y。首先来看一个例子:将数组[1,2,3,4,5,6,7,8]
中的元素均变为原来的2倍。
乍一看很简单,通过一个简单的循环便可以解决。那么我们首先用这个方法解决:
def num2(list):
newList = []
for x in list:
x *= 2
newList.append(x)
return newList
执行方法:
list = [1,2,3,4,5,6,7,8]
num2(list)
输出结果为:
[1,4,9,16,25,36,49,64]
上述方法为整形,若将其扩展为字符或字符串呢,我们再来看一个例子——大小写转换。
为了简单,我们只将['a','b','c','d']
转换为['A','B','C','D']
def upperNum(list):
newList = []
for x in list:
newList.append(x[:].upper())
return newList
执行函数:
list = ['a','b','c','d']
print upperNum(list)
输出结果为:
['A', 'B', 'C', 'D']
通过上述两个例子,你是否发现了相同点。均是一个数组中元素通过某种方法进行变换,唯一不同的是传入数组的类型与方法不同。那么,我们能否定义出这个相同点函数,将数组和方法作为参数传递进去。
幸运的是,泛型在python中非常简单,这让我们无需考虑数组中变量的类型。关于泛型的讨论,在下面补充中会介绍,如果你了解其他编程语言相信一定不会陌生。
而如何将函数作为参数传递进去,这便是函数式编程。
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
python对函数式编程提供部分支持,既可以作为参数传递,又能返回一个函数。由于python允许使用变量,因此,python不是纯函数式编程语言。
我们来定义一个map函数,其参数为function
方法和sequence
数组,而通过研究发现其返回值依然是是一个数组。
def map(function,sequence):
newList = []
for x in sequence:
newList.append(function(x))
return newList
上述两个通过map方法便能合二为一:
def num2(x):
return x * x
def upperNum(x):
return x[:].upper()
map(num2,list)
map(upperNum,list)
执行结果仍然为:
[1,4,9,16,25,36,49,64]
['A', 'B', 'C', 'D']
注,在python2中是能够直接出来结果的,但是在python3中这个会返回一个对象。要想用到结果就必须的在前面加上list
来转化一下,比如:
在系统库中的map函数定义如下,有一个可选参数:
map(function, sequence, *sequence_1)
如果给定多个序列,则函数被调用,其中包含相应的参数列表每个序列的项;当不是全部时,用None
代替缺失值使得序列的长度相同。如果函数没有,返回一个列表序列的项(或一个数组的列表,如果不止一个序列)。
例如:
l1 = [ 0, 1, 2, 3, 4, 5, 6 ]
l2 = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]
map(f, l1, l2)
结果为:
[(0, 'Sun'), (1, 'Mon'), (2, 'Tue'), (3, 'Wed'), (4, 'Thu'), (5, 'Fri'), (6, 'Sat')]
在补充中提到一个匿名函数,对map方法仍能进行进一步的简化。
filter()
filter 意思是过滤器,即为筛选出数组或元祖中符合条件的元素。首先来看一个例子,选出1-10中的奇数:
def is_Odd(list):
newList = []
for x in list:
if x%2 :
newList.append(x)
else:
pass
return newList
执行函数:
list = [1,2,3,4,5,6,7,8,9]
is_Odd(list)
输出结果为:
[1, 3, 5, 7, 9]
再来看另一个例子,假设有一个标记文件路径的数组字符串数组exampleFiles
赋值如下:
exampleFiles = ["README.md", "HelloWorld.py","HelloSwift.swift", "HelloPython.py"]
现在我们想从数组中取出.py数组,使用一个循环便可得到:
def getPyFile(fileNames):
newFileNames = []
for fileName in fileNames:
if ".py" in fileName:
newFileNames.append(fileName)
return newFileNames
执行这个函数:
fileNames = ["README.md", "HelloWorld.py","HelloSwift.swift", "HelloPython.py"]
getPyFile(fileNames)
结果为:
['HelloWorld.py', 'HelloPython.py']
从上述两个例子中可以得出两个方法的共同点,即返回那些函数(项)为真的序列项。如果函数是None
,返回True
的项,并返回一个列表。(如果序列是一个元组或者字符串,返回相同的类型)
因此,有了上述map函数的基础,我们可以定义一个过滤器方法,将上述方法合二为一:
def filter(function,sequence):
list = []
for x in sequence:
if function(x):
list.append(x)
return list
以筛选文件为例,执行这个方法:
def getPyFile2(fileName):
if ".py" in fileName:
return fileName
else:
pass
filter(getPyFile2,fileNames)
执行这个函数,结果为:
['HelloWorld.py', 'HelloPython.py']
与map相同,在python3中filter函数返回的是一个对象,需要加list
转换成数组。
reduce()
reduce意思是聚合,有了map和filter函数的研究基础,我们同样先来讨论一个简单的函数,定义一个函数计算数组中所有整数的和。
python函数库中虽然自带有sum()函数,但我们仍自定义函数解决
def sum(list):
result = 0
for x in list:
result += x
return result
执行函数:
list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
sum(list)
结果为:
45
再来看另外一个例子:数以一组单词,将其拼成一个句子。例如将数组["I","am","a","good","boy"]
拼接成一个字符串"I am a good boy"
定义函数如下:
def append(list):
result = ""
for x in list:
result = result + x+" "
return result
执行函数:
list = ["I","am","a","good","boy"]
append(list)
结果为:
"I am a good boy "
分析上面两个函数相同的地方,它们都用一些值初始化了一个变量result
,它们进行处理的时候都遍历了整个数组list
,并通过某种算法更新result
。要定义实现这种通用算法的一个通用函数,有两处需要进行抽象:result
的初始值以及在每个循环中用于更新result
值的函数。
因此我们可以定义一个函数函数满足上述需求,注意reduce函数最终返回的是一个value值而非数组:
def reduce(function,sequence):
result = None
for x in sequence:
result = function(result,x)
return result
其中function
为需要操作的函数,sequence
为数组,将result
与sequence
中元素操作后值赋予result
,进行循环操作。
执行上述函数操作便得以简化:
def sum(a,b):
return a + b
def append(a,b):
return a + " " + b
reduce(sum,list)
reduce(append,list)
这我们得出所需要的reduce函数。在系统中的reduce定义为 reduce(function,sequence,initial=None)
,由于初值并非只有0或空字符串,可以为任意,因此需要赋初值,在这里给予一个初值变量并默认初值。将上述reduce函数改编为:
def reduce(function,sequence,initial=None):
result = initial
for x in sequence:
result = function(result,x)
return result
补充
1.匿名函数
在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,需定义一个f(x)的函数,然后使用map函数,如:
def f(x):
return x * x
list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
输出结果为:
[1, 4, 9, 16, 25, 36, 49, 64, 81]
使用匿名函数,便能用一行解决:
list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
输出结果同样为:
[1, 4, 9, 16, 25, 36, 49, 64, 81]
从上述例子可以看到,匿名函数关键字为lambda
,冒号前面的x
表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数,例如:
f = lambda x: x * x
f(5)
结果为:
25
因此,上述几段程序使用匿名函数便能简化很多:
filter(lambda fileName:".py" in fileName,fileNames)
reduce(lambda a, b: a + b,list)
reduce(lambda a, b: a + " " +b,list)
2.sorted函数
将函数作为参数可不仅仅上述几个函数,比如sorted函数,顾名思义为排序,使用起来也很简单。
sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
在系统中,sort函数的定义为:
def sorted(iterable, cmp=None, key=None, reverse=False)
其高阶在于后面的三个参数。reverse参数是一个bool变量,是否倒序,默认值为False(正序)。key是关键字,是用于排序的对象。在上个例子中为数组本身,当然也可以对数组的个位数进行排序,如:
sorted([36, 5, 12, 9, 21],key=lambda x:x%10)
[21, 12, 5, 36, 9]
cmp参数是比较的方法,加入我们要实现倒序而不是用reverse,可以这样写:
sorted([36, 5, -12, 9, -21],cmp=lambda x,y:cmp(x,y))
[-21, -12, 5, 9, 36]
练习
作为本文的结束,给出一个小例子供大家理解三个函数。(题目来源于 objc.io出版的《函数式编程》)
有一组城市和人口数据如下:
name: "Paris", population: 2243
name: "Madrid", population: 3216
name: "Amsterdam", population: 811
name: "Berlin", population: 3397
假如我们想要找出至少有1百万人口的城市并打印出它们的城市名和人口,输出结果为:
City: Population
Paris : 2243000
Madrid : 3216000
Berlin : 3397000
首先我们过滤掉小于100万人口的城市。然后使用map
函数进行影射,将城市人口的单位进行转换。最后,我们使用reduce
通过城市名和人口的列表计算得出一个字符串。这里我们使用了标准库的map
,filter
和reduce
函数。结果,我们可以像链条一样将这些函数串起来。