函数式编程的核心是数据和映射,数据是Python的一切数据类型所表示的数据,映射就是函数。
理解函数式编程
若有列表对象:
a = {1,3,5,7,9,11}
现在以列表对象a为基础数据,创建一个列表对象b,a和b元素间的关系是:
b[i] = a[i]^2 其中 0<=i<6
a和b元素之间的关系就是映射,a和b是数据,a经过映射得到b。在函数式编程中,数据间的关系确定了映射的实现,也就是确定了函数的实现。
下面是列表对象a映射为列表对象b的函数:
def square(a):
b = list(map(lambda x:x*x,a))
return b
square()函数输入是a,输出是b,内部代码使用内置函数map和lambda表达式对输入a的每个元素进行平方计算,并将计算结果放置到一个迭代器,再使用内置函数list将迭代器构造为列表对象b,最后函数返回列表对象b。
lambda表达式是Python函数式编程的一部分,在后面的课程会讲到。
函数式编程的每个函数注重计算过程,在函数内部避免编写不必要的程序语句,尽量使用表达式完成计算。每个函数尽量保持独立,函数对传入的实参进行处理,不修改任何外部变量的值,并返回一个处理后的新值。
函数式编程对函数实现的约定,也符合映射的要求。映射是两个数据集合间的关系。假设有A和B两个数据集合,A通过映射F可以得到数据集合B,映射F输入的是A数据集合,输出的是B数据集合。
在整个映射过程,外部的变化不会影响A到B的映射,映射的输出仅依赖于映射的输入A,不依赖其他状态。例如前面定义的square(a)函数计算列表对象a每个元素的平方,只要a不变,函数不论什么时候调用,无论调用多少次,返回的值都是不变的。
前面的案例是比较简单的输入到输出的映射,映射用一个函数就可以实现。在实际编程中,一些输入需要多个映射才能得到预期的输出,这时就需要多个函数来实现。
例如:
编写一个程序,要求用户输入整数a,程序首先计算整数a的3次幂与a的取余,再将计算结果的自然对数赋值给整数b。
程序从输入a到输出b可以分为两个映射:
映射1:a到m,m等于整数a的3次幂与a的取余。
映射2:m到b,b等于数字m的自然对数。
定义映射1的函数:
def fa(a):
m = pow(a,3) / a
return m
定义映射2的函数:
def fb(fm,a):
return math.log(fm(a))
fb函数的第1个参数是一个fa函数,fm是fa函数的形参名称,在fb函数内部会先调用fm函数来计算输入a的3次幂与a的取余,然后调用math模块的log函数计算fm函数返回结果的自然对数。
函数的传递性
函数A作为函数B的参数称为函数的传递性,函数的传递性也是函数式编程的一部分。函数名称可以赋值给其它变量,被赋值的变量称为函数的引用,当该变量被使用时,就会执行变量所引用的函数。
一个需求变化的案例
#定义求两数和函数
def add(a,b):
return a+b;
#add函数名称赋值给temp变量
temp = add;
#使用temp变量
print(temp(-3,5))
#使用add函数
print(add(-3,5))
上面的代码定义了add函数,用于求两数的和,需要传入a和b两个参数。然后将add赋值给temp变量,此时temp变量指向了add函数,执行temp变量和执行add函数效果是一样的,调用的都是同一个函数,输出结果都是数字2。
现在需要对add函数进行改动,要求add函数求两数绝对值的和。我们可以在add函数中添加abs函数,先求出a和b的绝对值,然后再求和。
#定义求两数和函数
def add(a,b):
a = abs(a);
b = abs(b);
return a+b;
#add函数名称赋值给temp变量
temp = add;
#使用temp变量
print(temp(-3,5))
#使用add函数
print(add(-3,5))
在上面的代码中使用abs函数先求出a和b的绝对值,然后再求和,这样就解决了前面对add函数提出的需求。不过需求是不断变化的,现在新的需求又来了,要求add函数在支持绝对值求和的基础上,对a和b进行预处理,如果传入的是浮点,需要把浮点转换为整型。
通过函数参数传递函数
程序员需要如何应对这种不断变化的需求呢?唯一的办法是在编写程序时提高程序的灵活性和可扩展性,当需求发生变化时,尽量以小的代价满足用户需求。
在add函数的需求变化中,主要还是对传入的参数进行预处理。如果我们把参数预处理工作封装到另外一个函数中,将这个预处理函数和参数一同传给add函数,add函数仅执行求和操作就可以了,不需要关心传传入什么参数。把适应需求变化的工作都放在预处理函数中,这样就实现了add函数的灵活性和可扩展性。
#定义预处理函数
def prepare(a):
a = abs(a);
a = int(a);
return a;
#定义求两数和函数
def add(a,b,f):
return f(a)+f(b);
#add函数名称赋值给temp变量
temp = add;
#使用temp变量
print(temp(-3.15,5.89,prepare))
#使用add函数
print(add(-3.15,5.89,prepare))
这段代码相对前面的代码有了较大变动。定义了一个prepare函数,用于对传入add函数的参数做预处理工作。add函数增加了一个函数参数,这个函数参数用于接收另外一个函数的名称或引用变量,在add函数中,可以调用以参数方式传入的函数,调用方式和直接调用函数一样。
函数可以作为参数传递给调用函数,主要是函数的引用起了作用。当函数名称或引用变量做为实参传递给函数时,实际上是把函数的引用变量给传递过去了。
一个更实际的例子是用传入的转换函数简单将一个序列的数转换为相同的类型。例如传入Python的内建函数int()或float()来执行转换。
#定义转换函数
def convert(func,seq):
return list(map(func,seq))
myseq = (123,67.2,-6.2e8,89.9)
#将myseq转换为整型序列
print(convert(int,myseq))
#将myseq转换为浮点序列
print(convert(float,myseq))
上面的代码定义了convert转换函数,convert转换函数需要传递一个函数引用和列表序列进来,传递的函数引用可以是Python的内建函数int()或float(),传入的函数将传入的列表元素转换为整型或浮点类型。
上面的代码执行后,输出结果如下所示:
[123, 67, -620000000, 89]
[123.0, 67.2, -620000000.0, 89.9]
>>>