Part one -- 函数式编程的特性
part two -- Map
part three -- Reduce
part four -- Filter
part five -- Currying
函数式编程的特性
函数是一等公民,也就是说,你能应用到数据(对象)的事情,同样也能应用到函数上。比如说,把一个函数作为参数传递到另外一个函数。 使用递归进行主要的控制结构,在一些函数式编程语言(当然不包括Python)中,甚至没有“for”或者“while”循环这个概念。 列表(List)常常会作为数据处理的核心。 函数常常没有副作用,即对于某一个函数F,同样的输出往往会得到同样的结果,这是和数学上的函数等价的。因此,程序员需要充分考虑全局变量的必要性,能弃则弃。 函数式编程更关心程序要做什么,而不是怎么去做。 在函数式编程中,Higher order Function 承担着一个重要的角色。
通过上述的这些特性,函数式编程的往往能加快开发的效率,减少开发周期和提供更少BUG的、更具有可读性的代码。
支持函数式编程的语言不胜其数,比较流行的有Lisp、Scheme、Haskell、Erlang、Racket和Scala等等。当然,虽然Python不是一门函数式编程的语言,但也提供了许多支持函数式编程的特性。
Python函数式编程主由map, reduce, filter 和 apply四个函数以及Lambda表达式组成。别少看了这四个函数和一个表达式。单纯地使用它们,就可以实现所有的Python控制流操作(if, elif, else, assert, try, except, for, break, continue, while, def) 。虽说纯粹地使用它们来编写Python的程序十分奇怪(也不应该这样做),但是通过对这四个函数和一个算子的学习,我们不仅能通过它们的使用来缩短程序,增加可读性,还能学会函数式编程的基本思想,为以后其他语言的学习打下基础。
Lambda表达式
Lambda 表达式的语法十分简单:
lambda argvs: expression
Lambda表达式可以创造出一个匿名的函数对象,当然我们可以用变量来储存这个函数对象。
我们现在用Lambda表达式来创造函数add,这个函数接受两个参数x, y,返回x + y的结果。
add = lambda x, y: x + y`
add(3,4) # 结果为 7
上述程序等价于:
def add(x, y):
return x + y
add(3,4)
Map
Map函数的调用形式如下:
map(func, iterable)
Map需要两个必填的参数,一是函数名,二是一个可迭代的对象(Dict,Tuple或者List等)。Map的原理很简单,就是把可迭代对象里面的每一个元素都作为参数传入func中,并且依次地把执行func的结果组合成一个新的map对象,并返回(注意:在Python 2.x版本中, Map函数直接返回一个list对象)。
举一个 chestnut:
首先定义函数square,输入一个数字,返回该数字的平方:
def square(x):
return x * x
定义List:
l = [1, 2, 3, 4]
把square函数和l列表输入作为参数传入map函数,再把结果转换为List:
list(map(square, l))
最后得到如下结果:
[1, 4, 9, 16]
可以看到,没有用到以往经常使用的loop结构,我们便可以对list中的每一个数字都执行平方函数。当然,利用Lambda表达式,我们可以让该过程变得更加精简:
list(map(lambda x: x * x, l))
得到同样的结果。
Reduce
Reduce函数的调用形式如下:
reduce(func, iterable[, initializer])
同map函数相似reduce函数的第一个参数也是函数名,第二个参数为可迭代对象,第三个是初始数值为可选参数,当初始数值被空置了的时候,可迭代对象的第一个元素会被认为为初始数值。reduce函数利用输入的函数,对可迭代对象进行从左到右的累积运算,是一个很常见的操作。
使用Python 3.x的朋友要注意:reduce函数以及被从全局命名空间里移除了。在使用之前我们需要加上这一条:
from functools import reduce
还是举一个例子来解释一下吧,这次我们把一段List进行累加:
reduce(lambda x, y: x + y, [1, 2, 3, 4], 10))
结果为
10 + 1 + 2 + 3 + 4 = 20
Filter
Filter函数的调用形式如下:
filter(func, iterable)
filter()接受两个参数,第一个参数是一个函数名,第二个参数是一个可迭代的对象。
filter函数的功能与map相似,也是把可迭代对象里面的每一个元素都作为参数传入func中,并且依执行,如果func的返回值通常是一个bool值,如果bool值为true,该迭代对象里的元素会被保留。最后基于所有被保留的元素创建一个filter对象,并返回(注意:在Python 2.x版本中, Filter函数直接返回一个list对象)。
这次我们用filter函数来把列表中的奇数过滤掉:
list(filter(lambda x: x % 2 == 0, [1,2,3,4,5,6,7,8]))
结果:
[2, 4, 6, 8]
Currying
柯里化是一个可以让程序员提高函数的抽象度的其中一个手段。先从下面的几个例子来说明柯里化的用处吧!读者可以根据例子的要求先动手实验一下,这样能够加深印象,也便于理解。
首先我们想检验一个列表中的所有元素是否全为False,利用lambda表达式和三元表达式我们可以这样写:
check_all_true = lambda l: True if len(l) == 0 else (
False if l[0] == False else check_all_true(l[1:]))
这个函数比较复杂,没有使用loop控制结构来实现列表的遍历,而是使用了递归的方法,读者应当细细品味。
如果我们想要检查一个列表中的元素是否全都大于30呢?仿照上面的例子,我们可以这样写:
check_all_bigger_than_30 = lambda l: True if len(l) == 0 else (
False if l[0] <= 30 else check_all_bigger_than_30(l[1:]))
聪明的读者可能会发现,这两个函数是有其共同之处的。为了让日后的使用更加方便,我们可以尝试把这两个函数的公共模式提取出来,也就是抽象!那么,我们定义一个check函数。(为了让读者明白lambda表达式与python函数在功能上是等价,我们用def 来定义check函数。)
def check(filter_func, l):
if reduce(lambda x, y: filter_func(x) + filter_func(y), l, 0) == len(l):
return True
else:
return False
注意:在check函数中,我们没有使用递归了,而是利用reduce来遍历整个数组,而且实现方式也比较巧妙。我们利用了如下这一属性,如果filter_func应用到所有列表中的所有元素的结果都为True,那么reduce累加的结果应当等同于列表的长度。
好,那么我们现在可以利用check来检验一个列表中的所有元素是否全为False。 is_true = lambda x: x == True
check(isTrue, [1,0,1,0])
我们也可以这样实现check_all_bigger_than_30:
is_bigger_than_30 = lambda x: x > 30
check(is_bigger_than_30, a_list)
但是,我们似乎不能重新定义check_all_true和check_all_bigger_than_30。每一次想要实现check_all_true的功能,我们都要调用check(isTrue, a_list)。
那么,我们如何进一步提出这个公共模式,提高抽象程度呢?这里柯里化就派上用场了!我们重新定义check(为了不重名,我们把这个新的函数名为check_p)。
def check_p(f):
def _check(l):
if len(l) == 0:
return True
elif not f(l[0]):
return False
else:
return _check(l[1:])
return _check
注意:这次我们使用递归来遍历列表。更值得注意的是,check函数接受一个函数作为参数,返回一个函数。而这个返回的函数,接受一个列表,返回一个Boolean值。
有了Check_p函数,我们可以更加方便地重新定义check_all_true和check_all_bigger_than_30这两个函数:
check_all_true = check_p(is_true)
check_all_bigger_than_30 = check_p(is_bigger_than_30)
我们就可以这样使用这两个函数:
check_all_true([1,1,1,0])
check_all_bigger_than_30([32,12,88])
由此一来,我们就利用柯里化完成了对某一个公共模式的抽象。check_p函数接受一个函数f。这个函数f应当也接受一个参数,并对该参数进行判断从而返回一个Boolean值。然后,check_p返回一个函数,该返回函数接受一个列表作为参数,并利用函数f检查列表中的各个元素,返回检查结果。
在check_all_true的例子中,我们把isTrue作为函数f传入check_p,然后把check_p的返回函数赋值于check_all_true中,从而实现我们的目的。