本文主要介绍一下Python函数式编程的基本概念。

命令式编程

首先从大家熟悉的命令式编程开始,我们先回顾下平时在写代码时主要的情景。

其实,不管我们的业务代码有多复杂,都离不开以下几类操作:

1
2
3
1.函数定义:def
2.条件控制:if, elif, else
3.循环控制:for, break, continue, while

当然,这只是部分操作类型,除此之外还应该有类和模块、异常处理等等。但考虑到是入门,我们就先只关注上面这三种最常见的操作。

函数式编程

对应地,函数式编程也有自己的关键字。在Python语言中,用于函数式编程的主要由3个基本函数和1个算子。

1
2
1.基本函数:map()、reduce()、filter()
2.算子(operator):lambda

令人惊讶的是,仅仅采用这几个函数和算子就基本上可以实现任意Python程序。

当然,能实现是一回事儿,实际编码时是否这么写又是另外一回事儿。估计要真只采用这几个基本单元来写所有代码的话,不管是在表达上还是在阅读上应该都挺别扭的。不过,尝试采用这几个基本单元来替代上述的函数定义、条件控制、循环控制等操作,对理解函数式编程如何通过函数和递归表达流程控制应该会很有帮助。

替换条件控制语句

在对条件控制进行替换之前,我们先来回顾下Python中对布尔表达式求值时进行的“短路”处理。

什么叫“短路”处理?简单地讲,就是如下两点:

1.在f(x) and g(y)中,当f(x)为false时,不会再执行g(y),直接返回false
2.在f(x) or g(y)中,当f(x)为true时,不会再执行g(y),直接返回true
结论是显然易现的,就不再过多解释。

那么,对应到条件控制语句,我们不难理解,如下条件控制语句和表达式是等价的。

1
2
3
4
# flow control statement
if <cond1>: func1()
elif <cond2>: func2()
else: func3()

1
2
# Equivalent "short circuit" expression
(<cond1> and func1()) or (<cond2> and func2()) or (func3())

通过这个等价替换,我们就去除掉了if/elif/else关键词,将条件控制语句转换为一个表达式。而lambda算子返回的就是一个表达式。

基于这一点,我们就可以采用lambda创建如下函数。

1
2
3
4
5
6
7
8
9
10
>>> pr = lambda s:s
>>> print_num = lambda x: (x==1 and pr("one"))
.... or (x==2 and pr("two"))
.... or (pr("other"))
>>> print_num(1)
'one'
>>> print_num(2)
'two'
>>> print_num(3)
'other'

通过函数调用的结果可以看到,以上函数实现的功能与之前的条件控制语句实现的功能完全相同。

到这里,我们就实现了命令式条件控制语句向函数式语句的转换。并且这个转换的方法是通用的,所有条件控制语句都可以采用这种方式转换为函数式语句。

替换循环控制语句

接下来我们再看循环控制语句的转换。在Python中,循环控制是通过for和while这两种方式实现的。

##替换for循环

for循环语句的替换十分简单,采用map()函数就能轻松实现。这主要是因为for语句和map()原理相同,都是对可迭代对象里面的每一个元素进行操作,因此转换过程比较自然。

1
2
3
4
5
# statement-based for loop
for e in lst: func(e)

# Equivalent map()-based loop
map(func, lst)

1
2
3
4
5
6
7
8
9
10
>>> square = lambda x : x * x
>>> for x in [1,2,3,4,5]: square(x)
...
1
4
9
16
25
>>> map(square, [1,2,3,4,5])
[1, 4, 9, 16, 25]

替换while循环

while循环语句的替换相比而言就复杂了许多。

下面分别是while循环语句及其对应的函数式风格的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# statement-based while loop
while <condition>:
<pre-suite>
if <break_condition>:
break
else:
<suite>

# Equivalent FP-style recursive while loop
def while_block():
<pre-suite>
if <break_condition>:
return 1
else:
<suite>
return 0

while_FP = lambda: <condition> and (while_block() or while_FP())
while_FP()

这里的难点在于,函数式while_FP循环采用了递归的概念。当为true时,进入循环体,执行while_block();若为true时,返回1,while_FP()调用结束;若为false时,返回0,会继续执行or右侧的while_FP(),从而实现递归调用;若始终为false,则会持续递归调用while_FP(),这就实现了while语句中同样的功能。

为了对函数式的while循环有更深刻的理解,可以再看下如下示例。这个例子是在网上找的,实现的是echo功能:输入任意非”quit”字符时,打印输入的字符;输入”quit”字符时,退出程序。

1
2
3
4
5
6
7
8
9
10
11
12
➜  PythonFP python pyecho.py
IMP -- 1
1
IMP -- 2
2
IMP -- abc
abc
IMP -- 1 + 1
1 + 1
IMP -- quit
quit
➜ PythonFP

如下便是分别采用过程式和函数式语句实现的”echo”功能。

1
2
3
4
5
6
7
8
9
# imperative version of "echo()"
def echo_IMP():
while 1:
x = raw_input("IMP -- ")
print x
if x == 'quit':
break

echo_IMP()

1
2
3
4
5
6
7
def monadic_print(x):
print x
return x

# FP version of "echo()"
echo_FP = lambda: monadic_print(raw_input("FP -- "))=='quit' or echo_FP()
echo_FP()

参考资料

[^1]: (Python的函数式编程,从入门到放弃)