浅尝Python函数式编程

155 阅读7分钟
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中,从而实现我们的目的。