Python中的高阶函数

Python中的高阶函数

在熟悉了Python基础知识后,我们已经可以做一些脚本开发,或者简单的程序。然而,当我们开发较为复杂的程序时,仅使用基础知识内容就会显得比较吃力。这时,了解Python中的一些高级特性会使我们的开发过程变得简单和快乐。

在函数式编程中,我们可以将函数当做变量一样自由使用。一个函数接收另一个函数作为参数,这种函数称之为高阶函数(Higher-order-Functions)

看一个简单的例子:

1
2
def func(g, arr):
return [g(x) for x in arr]

上面的代码中func是一个高阶函数,它接收两个参数,第一个参数是函数,第二个参数是数组,func的功能是将函数g逐个作用于数组arr上,并返回一个新的数组。比如,我们可以这样用:

1
2
3
4
5
6
7
8
9
def double(x):
return 2 * x

def square(x):
return x * x

list = [1, 2, 3, 4]
arr1 = func(double, list) # arr1 = [2, 4, 6, 8]
arr2 = func(square, list) # arr2 = [1, 4, 9, 16]

说到高阶函数,就不得不提到闭包,这里介绍一下Python中闭包的定义:

如果在一个内部函数里,对外部作用域(但不是全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。

就拿此例来说,内部函数double中包含了对函数func中局部变量list的引用,这就是闭包。

map reduce filter sorted是Python中较为常用的内置高阶函数,它们为函数式编程提供了不少便利。

说明:本文介绍的内置高阶函数的定义可能会因为Python版本的不同而有所不同,文章以Python3.x版本中的定义为标准。

map

map函数的使用形式如下:

1
map(function, iterable, ...)

注意:这里函数一定要作为map的第一个参数,而不是第二个参数,否则会产生报错。

解释:function函数会作用于可迭代对象的每一个元素,生成结果,并返回一个迭代器。更加具体一点说就是map函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到Iterable的每个元素,并把结果作为新的Iterator返回。

举例说明,比如我们一个函数f(x)=x^2,要把这个函数作用在一个list[1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现。

现在,我们用Python代码实现:

1
2
3
4
5
6
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

map传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此需要通过list()函数让它把整个序列都计算出来并返回一个list。

你可能会想,不需要map函数,写一个循环,也可以计算出结果:

1
2
3
4
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
L.append(f(n))
print(L)

的确可以,但是,从上面的循环代码,能一眼看明白“把f(x)作用在list的每一个元素并把结果生成一个新的list”吗?明显可读性就差了很多。

所以,map作为高阶函数,体现了Python的设计原则优雅、明确、简单,事实上它把运算规则抽象化。因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把list中的所有数字转化为字符串格式:

1
2
>>> list(map(str, [1, 2, 3, 4]))
['1', '2', '3', '4']

只需一行代码。

看一些简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> def square(x):
... return x * x

>>> map(square, [1, 2, 3, 4])
<map at 0x106adfe48>
>>> list(map(square, [1, 2, 3, 4]))
[1, 4, 9, 16]

>>> list(map(lambda x: x * x, [1, 2, 3, 4])) # 使用 lambda
[1, 4, 9, 16]

>>> list(map(lambda x, y: x + y, [1, 2, 3, 4], [5, 6, 7, 8]))
[6, 8, 10, 12]

再来看个复杂一点例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def double(x):
return 2 * x

def triple(x):
return 3 *x

def square(x):
return x * x

funcs = [double, triple, square] # 列表元素是函数对象

# 相当于 [double(4), triple(4), square(4)]
value = list(map(lambda f: f(4), funcs))
print(value)

output:
[8, 12, 16]

最后我想要说明一点,迭代器有一个特点,就是所有的迭代器对象都可以作为next()内置函数的参数调用,每调用一次,就按角标顺序返回一个值,还是用代码讲吧:

1
2
3
4
5
6
iter = map(lambda x: x * x, [1, 2, 3, 4])
print(next(iter)) # 打印值为:1
print(next(iter)) # 打印值为:4
print(next(iter)) # 打印值为:9
print(next(iter)) # 打印值为:16
print(next(iter)) # 抛出StopIteration 异常

reduce

reduce函数的使用形式如下:

1
reduce(function, iterable[, initializer])

解释reduce函数必须接受两个参数,先将iterable的前两个item传给function,即function(item1, item2),函数的返回值和iterable的下一个item再传给function,即function(function(item1, item2), item3),如此迭代,直到iterable没有元素,如果有initializer,则作为初始值调用。

也就是说:

1
2
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
# 列表中是以从左到右作为优先顺序

看一些例子,就能很快理解了。

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> from functools import reduce
>>> reduce(lambda x, y: x * y, [1, 2, 3, 4]) # 相当于 ((1 * 2) * 3) * 4
24
>>> reduce(lambda x, y: x - y, [8, 5, 1], 20) # ((20 - 8) - 5) - 1
6
>>> f = lambda a, b: a if (a > b) else b # 两两比较,取最大值
>>> reduce(f, [5, 8, 1, 10])
10
>>> def fn(x, y): # 把序列[1, 3, 5, 7, 9]变换成整数13579
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579

filter

filter函数用于过滤元素,它的使用形式如下:

1
filter(function, iterable)

解释:和map类似,filter也接收一个函数和一个序列。但和map不同的是,filter把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。将function依次作用于iterable的每个item上,即function(item),用function返回值为True的item构成iterator作为filter的最终返回值。

看一些例子。

1
2
3
4
5
6
7
8
9
10
>>> even_num = list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6]))
>>> even_num
[2, 4, 6]
>>> odd_num = list(filter(lambda x: x % 2, [1, 2, 3, 4, 5, 6]))
>>> odd_num
[1, 3, 5]
>>> list(filter(lambda x: x < 'g', 'hijack'))
'ac'
>>> filter(lambda x: x < 'g', 'hijack')
<filter object at 0x1034b4080> # python3

可见用filter这个高阶函数,关键在于正确实现一个“筛选”函数。

注意到filter函数返回的同样是一个Iterator,也就是一个惰性序列,所以要强迫filter完成计算结果,需要用list()函数获得所有结果。

sorted

sorted函数用于对list进行排序,它的使用形式如下:

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

解释sorted有两个可选参数,必须指定为关键字参数。将key指定的函数作用于iterable的每一个元素上,并根据key函数返回的结果进行排序,最终返回一个新的排序列表。key默认值为None,即直接比较元素大小。

reverse是一个布尔值。如果设置为True,则列表元素将按照比较结果相反的方式进行排序。

看一些例子:

1
2
3
4
5
6
7
8
9
10
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

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

小结

  • 可接受其他函数作为参数的函数称为高阶函数;
  • map reduce filter sorted为函数式编程提供了不少便利,可使代码变得更简洁;
  • 通过map()来对Iterable中的每个元素进行相同的函数处理最终返回一个Iterator。
  • reduce()类似栈的思想,先让栈顶的两个元素出栈作为函数的两个参数,再将函数的返回值入栈,然后再让栈顶两个元素出栈,不断循环下去,直到栈里没有元素为止。
  • filter()的作用是从一个序列中筛选出符合条件的元素。由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。
  • sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数。

参考资料

  1. 高阶函数 - 廖雪峰的官方网站
  2. map/reduce/filter - Python 之旅 - 极客学院Wiki
  3. 高阶函数 - Python 之旅 - 极客学院Wiki
  4. Python笔记(二):高级特性之高阶函数 - 简书