Python函数式编程

Python 函数式编程

迭代器

首先要知道一个概念:可迭代对象(iterable object)

创建一个可迭代对象,需要在该类内部实现两个方法:

  • __iter__()
  • __next__()

Python 中 range, list, dict 等等都是可迭代对象

创建一个简单的迭代器对象MyRange类,模仿range的功能

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
class MyRange(object):
def __init__(self, n):
self.idx = 0
self.n = n

def __iter__(self):
return self

def __next__(self):
if self.idx < self.n:
val = self.idx
self.idx += 1
return val
else:
raise StopIteration()


print(MyRange(5))

for i in MyRange(5):
print(i, end=' ')

# Output:
# <__main__.MyRange object at 0x000001E1F11ED7F0>
# 0 1 2 3 4

StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代

生成器

生成器是一种用普通函数语法定义的迭代器,但是它只能迭代一次

Python’s generators provide a convenient way to implement the iterator protocol.

一般来说,带关键字yield的函数都是一个生成器

使用生成器函数可以节省内存,因为generator(param)不会真正执行函数内部代码,只有在被迭代时获取其中的值才会执行代码,且每次循环都返回一个值(即yield一个值),在处理完毕后下次循环时依然使用相同的内存(假设处理单位相同)来获取值并处理

yieldreturn的最大区别就是yield并不意味着函数的终止,而是意味着函数的一次挂起(可以理解为中断),在未被迭代完毕之前yield意味着先返回一次迭代值并继续下一次函数的执行

看一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> my_generator = (x*x for x in range(3))
>>> print(my_generator)
<generator object <genexpr> at 0x000001666A449510>
>>> for i in my_generator:
... print(i)
...
0
1
4
>>> for i in my_generator:
... print(i)
...
>>>

可见,再次使用迭代器 my_generator 是没有输出的

而且print(my_generator)的结果是一个生成器对象,并没有执行

该例子特别之处在于对my_generator赋值时使用的是()而不是[],因而生成的是一个generator对象

实现斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def fibonacci(n):
a, b, cnt = 0, 1, 0
while cnt < n:
yield a
a, b = b, a + b
cnt += 1


f = fibonacci(8)
for i in range(10):
print(f.__next__(), end=' ')

OUTPUT:
0 1 1 2 3 5 8 13 Traceback (most recent call last):
File "____", line _, in <module>
print(f.__next__(), end=' ')
StopIteration

实例可见,迭代到 yield 不在生成任何对象时,会抛出StopIteration异常

实现 numpy 库中的 flatten()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy as np

a = np.arange(1, 9, 1).reshape((2, 2, 2))

def my_flatten(nested):
try:
for sublist in nested:
for ele in my_flatten(sublist):
yield ele
except TypeError:
yield nested

print(my_flatten(a)) # <generator object my_flatten at 0x00000175CD4A1AC0>
print([i for i in my_flatten(a)]) # [1, 2, 3, 4, 5, 6, 7, 8]
print(list(a.flatten()) == [i for i in my_flatten(a)]) # True

生成器方法

send()

生成器开始运行后,可以从外部向其提供一个值,使用send()方法实现

注意,当生成器被挂起后使用send才有意义
若非要在生成器刚刚启动时使用,可以使用send()传递None

看一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
def fun2():
val = yield 'value'
yield val

f = fun2()
print(f.__next__()) # value
print(f.send('message')) # message

"""
以上两句等价于
print(f.send(None)) # value
print(f.send('message')) # message
"""

以如下示例来解析send()作用与执行过程

1
2
3
4
5
6
7
8
9
10
def fun():
val1 = yield 'generator'
val2 = yield val1
yield val2


f = fun()
print(f.send(None)) # generator
print(f.send('message1')) # message1
print(f.send('message2')) # message2

执行流程

  1. 首先,构建生成器函数fun,并利用器创建生成器对象f
  2. 使用生成器 f 调用无参的 send() 函数,其功能和 __next__() 函数完全相同,因此开始执行生成器函数,执行第一个 yield 'generator' 语句,该语句会返回 'generator' 字符串,然后程序在此处挂起(注意,此时还未执行对 val1 的赋值操作)
  3. 下面开始使用生成器 f 调用有参的 send() 函数,首先它会将挂起的程序唤醒,同时还会将其参数message1赋值给当前 yield 语句的接收者,也就是 val1 变量。程序一直执行完 yield val1 再次暂停,因此会输出message1
  4. 最后依旧是调用有参的 send() 函数,同样它会启动餐厅的程序,同时将参数message2传给 val2,然后执行完 yield val2 后(输出 message2),程序执行再次被挂起

若在程序末尾添加语句,如下

1
print(f.send('message3'))

则会抛出异常StopIteration

1
2
3
4
Traceback (most recent call last):
File "___", line _11_, in <module>
print(f.send('message3'))
StopIteration

throw()

该方法可以在生成器(yield表达式)中引发异常,调用时可以提供一个异常类型、一个可选值和一个traceback对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def foo():
try:
yield 1
except ValueError:
print('捕获到 ValueError')


f = foo()
print(next(f))
f.throw(ValueError)
# OUTPUT:
# Traceback (most recent call last):
# File "___", line _, in <module>
# f.throw(ValueError)
# StopIteration
# 1
# 捕获到 ValueError

如果到剩余代码执行完毕没有遇到下一个 yield 语句,则程序会抛出 StopIteration 异常

close()

用于停止生成器,调用时无需提供参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def foo():
try:
yield 1
except GeneratorExit:
print('捕获到 GeneratorExit')
print('Continue...')
yield 1
print('Done!')


f = foo()
print(next(f))
f.close()

# OUTPUT:
# Traceback (most recent call last):
# File "___", line _, in <module>
# f.close()
# RuntimeError: generator ignored GeneratorExit
# 1
# 捕获到 GeneratorExit
# Continue...

当程序在生成器函数中遇到 yield 语句暂停运行时,此时如果调用 close() 方法,会阻止生成器函数继续执行,该函数会在程序停止运行的位置抛出 GeneratorExit 异常

虽然通过捕获 GeneratorExit 异常,可以继续执行生成器函数中剩余的代码,带这部分代码中不能再包含 yield 语句,否则程序会抛出 RuntimeError 异常

生成器函数一旦使用 close() 函数停止运行,后续将无法再调用 next() 函数或者 __next__() 方法启动执行,否则会抛出 StopIteration 异常

匿名函数 – lambda

格式

1
lambda [parameter_list]: expression

先看一个简单的例子

1
lambda x: x ** 2

传入参数 3

1
2
>>> (lambda x: x ** 2)(3)
9

这样可以执行是因为 lambda 函数是一个表达式,可以被命名,因此可以效仿如下写法

1
2
3
>>> square = lambda x: x ** 2
>>> square(3)
9

上面的代码等价于以下写法

1
2
def square(x):
return x ** 2

Lambda 函数经常与高阶函数一起使用

内置高阶函数

map

语法

1
2
3
4
5
6
map(func, *iterables) --> map object

"""
Make an iterator that computes the function using arguments from
each of the iterables. Stops when the shortest iterable is exhausted.
"""

例子

1
2
>>> print(list(map(lambda x: x**2, list(range(3)))))
[0, 1, 4]

函数可以接受多个可迭代对象

1
2
>>> print(list(map(lambda x, y: x + y < 10, list(range(10)), list(range(10)))))
[True, True, True, True, True, False, False, False, False, False]

filter

语法

1
2
3
4
5
filter(function or None, iterable) --> filter object
"""
Return an iterator yielding those items of iterable for which function(item)
is true. If function is None, return the items that are true.
"""

例子

1
2
>>> print(list(filter(lambda x: x % 2 == 0, range(10))))
[0, 2, 4, 6, 8]

reduce

reduce() 函数在 Python 3.x 中已经被移除,放入了 functools 模块,因此在使用该函数之前,需先导入 functools 模块

语法

1
2
3
4
5
6
7
8
9
10
reduce(function, sequence[, initial]) -> value

"""
Apply a function of two arguments cumulatively to the items of a sequence,
from left to right, so as to reduce the sequence to a single value.
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the sequence in the calculation, and serves as a default when the
sequence is empty.
"""

例子

1
2
3
>>> from functools import reduce
>>> print(reduce(lambda x, y: x * y, list(range(1, 5))))
24

sorted

语法

1
2
3
4
5
6
7
8
sorted(iterable, cmp=None, key=None, reverse=False)

"""
Return a new list containing all items from the iterable in ascending order.

A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.
"""

参数说明:

  • iterable – 可迭代对象
  • cmp – 比较的函数
  • key – 主要是用来进行比较的元素
  • reverse – 排序规则(True 降序,默认 False 升序)

例子

1
2
3
4
5
6
7
8
from random import randint

arr = [randint(-100, 100) for _ in range(5)]

if __name__ == '__main__':
print(arr) # [-90, 60, 61, 18, 26]
print(sorted(arr, key=abs)) # [18, 26, 60, 61, -90]
print(sorted(arr, key=abs, reverse=True)) # [-90, 61, 60, 26, 18]

zip

语法

1
2
3
4
5
6
7
8
zip(*iterables) --> A zip object yielding tuples until an input is exhausted.

"""
The zip object yields n-length tuples, where n is the number of iterables
passed as positional arguments to zip(). The i-th element in every tuple
comes from the i-th iterable argument to zip(). This continues until the
shortest argument is exhausted.
"""

例子

1
2
>>> print(list(zip(range(5), range(0, 5, 1))))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

组合使用

列举一个经典例子

sorted + lambda 用于字典排序

1
2
3
>>> d = {'a': 8, 'b': 5, 'c': 9, 'd': 10, 'e': 3}
>>> print(sorted(d.items(), key=lambda k: k[1]))
[('e', 3), ('b', 5), ('a', 8), ('c', 9), ('d', 10)]