函数式编程
函数是 Python 内建支持的一种封装,把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如 y = x * x
函数计算 x
的平方根,只要 x
的平方,不论什么时候调用,调用几次,值都是不变的。
Python 对函数式编程提供部分支持,由于 Python 允许使用变量,因此 Python 不是纯函数式编程语言。举例来说,现在有这样一个数学表达式:
1 | (1 + 2) * 3 - 4 |
传统的过程式编程,可能这样写:
1 | var a = 1 + 2; |
函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:
1 | var result = subtract(multiply(add(1,2), 3), 4); |
这段代码再演进一下,可以变成这样:
1 | add(1,2).multiply(3).subtract(4) |
这基本就是自然语言的表达了,再看下面的代码,应该一眼就能明白它的意思:
1 | merge([1,2],[3,4]).sort().search("2") |
因此,函数式编程的代码更容易理解。但是如果要想学好函数式编程,建议使用 Erlang ,Haskell 而不是 Python 。
高阶函数
Python 中变量可以指向函数,函数名实际上也是变量。以 Python 内置的求绝对值的函数 abs()
为例:
1 | >>> abs(-10) |
由此可见 abs(-10)
是函数调用,而 abs
是函数名称。可以理解为 abs
等同于变量名,并且函数名(变量名)相当于是一个标签,贴在了内存中函数体(变量的值)上。
如果要获得函数调用结果,可以把函数执行的结果赋值给变量:
1 | >>> x = abs(-10) |
函数名称也可以赋值给变量,实际上相当于重新建立了一个对象引用,并且指向了函数名所指向的对象上,也就是函数体:
1 | >>> id(abs) |
高阶函数:既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数的函数名作为参数,这种函数就称之为高阶函数 。编写高阶函数,就是让函数的参数能够接收别的函数。
高阶函数至少满足下列条件之一:
接受一个或多个函数名作为输入 。
输出(返回)一个函数对象。
编写高阶函数,就是让函数的参数能够接收别的函数。以简单的加法器为例:
1 | # -*- coding:utf-8 -*- |
返回函数对象的示例:
1 | # -*- coding:utf-8 -*- |
Python内置的高阶函数
filter
1 | filter(function, sequence) |
filter 会对 sequence 中的 item 依次执行 function(item),将执行结果为 True 的 item 做成一个 filter object 的迭代器返回。可以看作是过滤函数。请注意, filter(function, sequence)
相当于一个生成器表达式,当 function
不是 None
的时候实际上就是 (item for item in iterable if function(item))
;function
是 None
的时候实际上就是 (item for item in iterable if item)
。
1 | lst = ['a', 'b', 'c', 'd', 'e'] |
把一个序列中的空(空或空白)字符串删掉,可以这么写:
1 | #!/usr/bin/env python3 |
如果只删除空字符串而保留空白字符串:
1 | retval = list(filter(None, ['A', '', 'B', None, 'C', ' '])) |
map
1 | map(function, sequence) |
map 会对 sequence 中的 item 依次执行 function(item),将执行结果组成一个 map object 的迭代器返回,其功能近似于不带条件,只有循环的生成器生成式。
1 | lst = ['a','b','c','d','e'] |
map 也支持多个 sequence,这就要求 function 也支持相应数量的参数输入:
1 | ef add(x,y): |
我们不但可以使用 map 函数计算简单的算术运算,还可以计算任意复杂的函数,比如把一个 list 所有数字转为字符串:
1 | 1, 2, 3, 4, 5, 6, 7, 8, 9])) list(map(str, [ |
sorted
1 | sorted(iterable, /, *, key=None, reverse=False) |
sorted 会返回一个列表对象。
列表对象的 sort 方法是在源数据的基础上进行的排序,因此源列表发生了变化。
使用
sorted()
方法会重新生成一个新的对象,不会改变原数据。使用
sorted()
时内存会有两份数据容易造成内存空间占用过高
对 list 对象进行排序:
1 | >>> sorted([36, 5, -12, 9, -21]) |
sorted 可以接收一个 key
参数来实现自定义的排序,例如按绝对值大小排序:
1 | >>> sorted([36, 5, -12, 9, -21], key=abs) |
key
指定的函数将作用于 list 对象的每一个元素上,并根据函数返回的结果进行排序。对比原始的 list 和经过key=abs
处理过的 list :
1 | lst = [36, 5, -12, 9, -21] |
再比如字符串排序的例子:
1 | >>> sorted(['bob', 'about', 'Zoo', 'Credit']) |
默认情况下,对字符串排序,是按照ASCII的大小比较的,由于 'Z' < 'a'
,结果是大写字母 Z
会排在小写字母 a
的前面。
现在,想要再排序时忽略大小写,按照字母顺序来进行排序。要实现这个算法,不必对现有代码做太多的改动,只要能用一个函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),然后再比较,即可实现忽略大小写的排序:
1 | >>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) |
要进行反向排序,不必改动函数,可以传入第三个参数 reverse=True
:
1 | >>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) |
从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且核心代码可以保持得非常简洁。
reduce
1 | reduce(function, sequence, starting_value) |
reduce 也是高阶函数之一,它将会对 sequence 中的 item 顺序迭代调用 function ,如果有 starting_value,还可以作为初始值进行调用。
比如,对一个序列求和就可以用 reduce
来实现 ,当然求和运算可以直接用 Python 的内建函数 sum()
,没必要动用 reduce
。这里只是为了做个示范:
1 | from functools import reduce |
但是如果要把序列 [1, 3, 5, 7, 9]
变换成整数 13579
,reduce
就可以派上用场:
1 | from functools import reduce |
假设Python中没有 int()
函数,我们完全可以使用 map 和 lambda 实现这一功能:
1 | from functools import reduce |