Python的高阶函数

函数式编程

函数是 Python 内建支持的一种封装,把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。

函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其他状态。比如 y = x * x 函数计算 x 的平方根,只要 x 的平方,不论什么时候调用,调用几次,值都是不变的。

Python 对函数式编程提供部分支持,由于 Python 允许使用变量,因此 Python 不是纯函数式编程语言。举例来说,现在有这样一个数学表达式:

1
(1 + 2) * 3 - 4

传统的过程式编程,可能这样写:

1
2
3
var a = 1 + 2;
var b = a * 3;
var c = b - 4;

函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:

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
2
3
4
>>> abs(-10)
10
>>> abs
<built-in function abs>

由此可见 abs(-10) 是函数调用,而 abs 是函数名称。可以理解为 abs 等同于变量名,并且函数名(变量名)相当于是一个标签,贴在了内存中函数体(变量的值)上。

如果要获得函数调用结果,可以把函数执行的结果赋值给变量:

1
2
3
>>> x = abs(-10)
>>> x
10

函数名称也可以赋值给变量,实际上相当于重新建立了一个对象引用,并且指向了函数名所指向的对象上,也就是函数体:

1
2
3
4
5
6
7
8
9
>>> id(abs)
10652528
>>> f = abs
>>> id(f)
10652528
>>> f(-10)
10
>>> f
<built-in function abs>

高阶函数:既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数的函数名作为参数,这种函数就称之为高阶函数 。编写高阶函数,就是让函数的参数能够接收别的函数。

高阶函数至少满足下列条件之一:

  • 接受一个或多个函数名作为输入 。

  • 输出(返回)一个函数对象。

编写高阶函数,就是让函数的参数能够接收别的函数。以简单的加法器为例:

1
2
3
4
5
6
# -*- coding:utf-8 -*-
def add(x,y,f):
return f(x) + f(y)

sum = add(3,-6,abs)
print(sum)

返回函数对象的示例:

1
2
3
4
5
6
7
8
9
10
11
12
# -*- coding:utf-8 -*-
def foo():
x=3
def bar():
return x
return bar
x = foo
print(x())
x = foo()
print(x())
# <function foo.<locals>.bar at 0x00947E40>
# 3

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))functionNone 的时候实际上就是 (item for item in iterable if item)

1
2
3
4
5
6
7
8
9
10
11
12
lst = ['a', 'b', 'c', 'd', 'e']


def func(x):
if x != 'a':
return x # 这两行可以简写成 return x != 'a'


retval = filter(func, lst)

print(retval) # <filter object at 0x00F866F0> 一个 filter object 迭代器对象
print(list(retval)) # ['b', 'c', 'd', 'e']

把一个序列中的空(空或空白)字符串删掉,可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

lst = ['A', '', 'B', None, 'C', ' ']


def notNull(x):
return x and len(x.strip()) > 0


print(list(filter(notNull, lst)))
# 结果: ['A', 'B', 'C']

如果只删除空字符串而保留空白字符串:

1
2
3
4
5
retval = list(filter(None, ['A', '', 'B', None, 'C', '  ']))
# None代表不输入函数,也就是
# [x for x in ['A', '', 'B', None, 'C', ' '] if x ]

print(retval)

map

1
map(function, sequence)

map 会对 sequence 中的 item 依次执行 function(item),将执行结果组成一个 map object 的迭代器返回,其功能近似于不带条件,只有循环的生成器生成式。

1
2
3
4
5
6
7
8
lst = ['a','b','c','d','e']

def func(x):
return '<-' + x + '->'
retval = map(func,lst)

print(retval) # <map object at 0x00D76770> 一个 map object 迭代器对象
print(list(retval)) # ['<-a->', '<-b->', '<-c->', '<-d->', '<-e->']

map 也支持多个 sequence,这就要求 function 也支持相应数量的参数输入:

1
2
3
4
ef add(x,y):
return x+y
print (list(map(add, range(10), range(10))))
#[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

我们不但可以使用 map 函数计算简单的算术运算,还可以计算任意复杂的函数,比如把一个 list 所有数字转为字符串:

1
2
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

sorted

1
sorted(iterable, /, *, key=None, reverse=False)

sorted 会返回一个列表对象。

  • 列表对象的 sort 方法是在源数据的基础上进行的排序,因此源列表发生了变化。

  • 使用 sorted() 方法会重新生成一个新的对象,不会改变原数据。

  • 使用 sorted() 时内存会有两份数据容易造成内存空间占用过高

对 list 对象进行排序:

1
2
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]

sorted 可以接收一个 key 参数来实现自定义的排序,例如按绝对值大小排序:

1
2
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

key 指定的函数将作用于 list 对象的每一个元素上,并根据函数返回的结果进行排序。对比原始的 list 和经过key=abs 处理过的 list :

1
2
3
4
5
6
7
8
9
lst = [36, 5, -12, 9, -21]

print(lst)
print(sorted(lst, key=abs))

'''
[36, 5, -12, 9, -21]
[5, 9, -12, -21, 36]
'''

再比如字符串排序的例子:

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

默认情况下,对字符串排序,是按照ASCII的大小比较的,由于 'Z' < 'a',结果是大写字母 Z 会排在小写字母 a 的前面。

现在,想要再排序时忽略大小写,按照字母顺序来进行排序。要实现这个算法,不必对现有代码做太多的改动,只要能用一个函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),然后再比较,即可实现忽略大小写的排序:

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

要进行反向排序,不必改动函数,可以传入第三个参数 reverse=True

1
2
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且核心代码可以保持得非常简洁。

reduce

1
reduce(function, sequence, starting_value)

reduce 也是高阶函数之一,它将会对 sequence 中的 item 顺序迭代调用 function ,如果有 starting_value,还可以作为初始值进行调用。

比如,对一个序列求和就可以用 reduce 来实现 ,当然求和运算可以直接用 Python 的内建函数 sum() ,没必要动用 reduce 。这里只是为了做个示范:

1
2
3
4
5
6
7
8
9
10
from functools import reduce

def add(x,y):
return x+y

print (reduce(add, range(1, 101)))
# 5050 (注:1+2+...+99+100)

print (reduce(add, range(1, 101), 20))
# 5070 (注:20+1+2+...+99+100)

但是如果要把序列 [1, 3, 5, 7, 9] 变换成整数 13579reduce 就可以派上用场:

1
2
3
4
5
6
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579

假设Python中没有 int() 函数,我们完全可以使用 map 和 lambda 实现这一功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from functools import reduce

seq = ['1', '3', '5', '7', '9']

DIGITS = {
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9
}


def char2num(x):
return DIGITS[x]


def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))


print(str2int(seq)) # 13579
有钱任性,请我吃包辣条
0%