纸上得来终觉浅,绝知此事要躬行。
1. 迭代器
在 Python 这门语言中,迭代器毫无疑问是最有用的特性之一,所以为了更好的理解生成器,我们就需要好好的从基础开始说起。
[1] 可迭代 - Iterable
- 迭代器协议是指对象需要提供
next
方法,它要么返回迭代中的下一项,要么就引起一个StopIteration
异常,以终止迭代。 - 在
Python
中任意的对象,只要它定义了可以返回一个迭代器的__iter__
方法,或者支持下标索引的__getitem__
方法,那么它就是一个可迭代对象。 - 需要我们注意的就是,可迭代的不等同于迭代器,所以只定义了
__iter__
或者__getitem__
方法的对象并不是迭代器。
In [1]: L1 = [1, 2, 3]
# 列表定义了__iter__这个方法,但是它并不是一个迭代器
In [2]: L1.__iter__
Out[2]: <method-wrapper '__iter__' of list object at 0x104c06d48>
In [3]: next(l)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-3-cdc8a39da60d> in <module>()
----> 1 next(l)
NameError: name 'l' is not defined
# 随意需要手动将列表转化为可迭代对象
In [4]: L2 = iter(L1)
In [5]: next(L2)
Out[5]: 1
In [6]: next(L2)
Out[6]: 2
In [7]:L2.__next__()
Out[7]: 3
In [8]: next(L2)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-8-632a566062d6> in <module>()
----> 1 next(L2)
StopIteration:
# 可以看到L2的类型就是迭代器
In [9]: L2
Out[9]: <list_iterator at 0x104cb3470>
# 而L1却只是一个普通的列表对象
In [10]: L1
Out[10]: [1, 2, 3]
[2] 迭代器 - Iterators
- 实现了
__iter__
和next
方法的对象就是迭代器,其中,__iter__
方法返回迭代器对象本身,next
方法返回容器的下一个元素,在没有后续元素时抛出StopIteration
异常。 - 需要注意的是,在
Python2
中要定义的next
方法名字不同,应该使用__next__
方法表示,带了双下划线。 - 虽然列表和字典本身已经实现了
__iter__
的可迭代,但它们的输出结果都是一次性获取值,而迭代器是按需进行生成的。如果数量集很大的话,就会占用很大的内存消耗,且使用迭代器可进行定制化、更方便、更优雅,所以使用场景很多。
class Fib:
def __init__(self, max):
self.a = 0
self.b = 1
self.max = max
def __iter__(self):
return self
def next(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib
In [16]: from iterable import Fib
In [17]: f = Fib(100)
In [18]: for i in f:
...: print(i)
...:
0
1
1
2
3
5
8
13
21
34
55
89
In [19]: list(Fib(100))
Out[19]: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
2. 生成器
生成器(Generator)是一种使用普通函数语法定义的迭代器。生成器和普通函数的区别是使用 yield,而不是 return 返回值。
生成器的含义
- 在汉语中,
yield
有生成的含义。yield
的语句一次返回一个结果,在每个结果的中间会挂起函数的转态,再次调用则继续执行。 - 生成器的本身其实还是一个迭代器,只不过
yield
的这种写法更为表达更为简洁且表达性更强。
In [1]: def gen():
...: yield 1
...: yield 2
...:
In [2]: g = gen()
In [3]: next(g)
Out[3]: 1
In [4]: g.__next__()
Out[4]: 2
In [5]: for i in gen():
...: print(i)
...:
1
2
- 简单地讲,
yield
的作用就是把一个函数变成一个generator
,带有yield
的函数不再是一个普通函数,Python
解释器会将其视为一个generator
,返回一个iterable
对象。 - 在
for
循环执行时,每次循环都会执行函数内部的代码,执行到yield x
时,函数就返回一个迭代值,下次迭代时,代码从yield x
的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到yield
。
# 实现斐波拉切数列的生成器版本
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
生成器表达式
- 生成器表达式类似于列表解析式和字典解析式,除了名称上的不同以外用法基本是一样的,会按需返回一个生成器,写法更为简单明了。
In [6]: g = ( i for i in range(5) if i % 2 )
In [7]: g
Out[7]: <generator object <genexpr> at 0x108451360>
In [8]: for i in g:
...: print(i)
...:
1
3
In [9]: next(g)
---------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-9-e734f8aca5ac> in <module>()
----> 1 next(g)
StopIteration:
In [10]: g = ( i for i in range(5) if i % 2 )
In [11]: next(g)
Out[11]: 1
In [12]: g.__next__()
Out[12]: 3
使用 yield from 语法
yield from
表达式允许一个生成器代理另一个生成器,这样就允许生成器被替换为另一个生成器,子生成器允许返回值。yield
返回一个生成器 , 这个生成器就是range
自身,yield from
也返回一个生成器,这个生成器是由range
代理的。
def gen1(x):
yield range(x)
def gen2(x):
yield from range(x)
it1 = gen1(5)
it2 = gen2(5)
>>> print( [ x for x in it1] )
[range(0, 5)]
>>> print( [ x for x in it2] )
[0, 1, 2, 3, 4]
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
判断函数是否为生成器
- 要注意区分
fab
和fab(5)
,fab
是一个generator function
,而fab(5)
是调用fab
返回的一个generator
,好比类的定义和类的实例的区别。 - 在一个
generator function
中,如果没有return
,则默认执行至函数完毕,如果在执行过程中return
,则直接抛出StopIteration
终止迭代。
>>> from inspect import isgeneratorfunction
>>> isgeneratorfunction(fab)
True
>>> import types
>>> isinstance(fab, types.GeneratorType)
False
>>> isinstance(fab(5), types.GeneratorType)
True
# fab是无法迭代的,而fab(5)是可迭代
>>> from collections import Iterable
>>> isinstance(fab, Iterable)
False
>>> isinstance(fab(5), Iterable)
True
3. 协程
协程(Coroutine)和生成器很类似,都是包含了 yield 关键字的函数。但协程中的 yield 通常都会在表达式的右侧出现。
协程的特性优点
- 相对于多线程而言,协程的好处在于其在一个线程里面执行避免了线程之间切换带来的额外开销。而且多线程中会使用共享的资源,往往需要加锁,而协程不需要。
- 因为协程的代码逻辑是事先写好的且可以预料到,并不会存在在同一时间多个线程去写同一个共享变量,而导致一些资源强占的问题,当然就没有必要加锁了。
# 协程中的yield通常都会在表达式的右侧
In [1]: def coroutine():
...: print('Start coroutine...')
...: x = yield
...: print(f'Received: {x}')
...:
In [2]: coro = coroutine()
In [3]: coro
Out[3]: <generator object coroutine at 0x1056d9f68>
# 通常需要使用next执行而启动协程
In [4]: next(coro)
Start coroutine...
In [5]: coro.send(1)
Received: 1
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-5-e272bd1527da> in <module>()
----> 1 coro.send(1)
StopIteration:
协程的使用方法
- 使用
send
方法可以将值传递给协程,收到值之后,yield
右侧的值会先返回,再将状态挂起,之后往复交替知道执行完毕抛出StopIteration
错误。
In [8]: def coroutine(a):
...: print(f'Start coroutine: {a}')
...: b = yield a
...: print(f'Received: b={b}')
...: c = yield a + b
...: print(f'Received: c={c}')
...:
In [9]: coro = coroutine(1)
In [10]: next(coro)
Start coroutine: 1
Out[10]: 1
In [11]: coro.send(2)
Received: b=2
Out[11]: 3
In [12]: coro.send(10)
Received: c=10
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-12-8e657389bc11> in <module>()
----> 1 coro.send(10)
StopIteration:
协程可以将异步的编程同步化
- 回调函数常常是异步编程使用的一种手法,主线程发起一个任务让其自行工作,当这个任务完成之后会调用预先指定的回调函数完成后续的任务,然后返回到主线程中。这样的好处在于,主线程无须等待可以处理其他任务。但这样回调的方式,会让人不舒服且不友好。
- 使用协程就不需要在使用回调函数了,用
yield
就可以改善程序的结构设计。我们使用协程就可以实现,主程序和子程序之前的交互,通过yield
和send
完成数据的传递,完成相同的工作。
# 回调的示例
In [20]: def framework(logic, callback):
...: s = logic()
...: print(f'[FX] logic: {s}')
...: print(f'[FX] do something...')
...: callback(f'async: {s}')
...:
In [21]: def logic():
...: return 'Logic'
...:
In [22]: def callback(s):
...: print(s)
...:
In [23]: framework(logic, callback)
[FX] logic: Logic
[FX] do something...
async: Logic
# 协程的示例,framework是主线程,logic是协程
In [24]: def framework(logic):
...: try:
...: it = logic()
...: s = next(it)
...: print(f'[FX] logic: {s}')
...: print(f'[FX] do something...')
...: it.send(f'async: {s}')
...: except StopIteration:
...: pass
...:
In [25]: def logic():
...: s = 'Logic'
...: r = yield s
...: print(r)
...:
In [26]: framework(logic)
[FX] logic: Logic
[FX] do something...
async: Logic
协程完成生产者和消费者
- 简单示例协程的工作方式
# 当然在一个线程中这样写是不正确的,因为协程中使用了while True已经阻塞的当前的线程了,需要使用两个线程。但如下所示的协程,就可以在一个线程使用哈。
In [30]: def consumer():
...: while True:
...: value = yield
...: print(f'consume: {value}')
...:
In [31]: def producer(c):
...: for i in range(10, 13):
...: c.send(i)
...:
# 生成一个协程
In [32]: c = consumer()
# 类似于next方法启动协程
In [33]: c.send(None)
In [34]: producer(c)
consume: 10
consume: 11
consume: 12
# 关闭协程
In [35]: c.close()
- 协程在消费者和生产者之间切换执行
In [40]: def consumer():
...: req = 'Start consumer ...'
...: while True:
...: value = yield req
...: print(f'consumer: {value}')
...: req = f'Result: {value * 2}'
...:
In [41]: def producer(c):
...: for i in range(10, 13):
...: print(f'Producing {i} ...')
...: req = c.send(i)
...: print(f'Consumer return: {req}')
...:
In [42]: c = consumer()
In [43]: c.send(None)
Start consumer ...
In [44]: producer(c)
Producing 10 ...
consumer: 10
Consumer return: Result: 20
Producing 11 ...
consumer: 11
Consumer return: Result: 22
Producing 12 ...
consumer: 12
Consumer return: Result: 24