迭代器、生成器和协程
可迭代(Iterable)
Python 中任意的对象, 只要定义了可以返回一个迭代器的 __iter__方法, 或者支持下标索引的 __getitem__ 方法, 那么它就是一个可迭代对象。
有些对象定义了 iter 和 getitem 这两种方法, 但是返回的不是一个迭代器:
>>> l = [1, 2, 3]
>>> l.__iter__ <method-wrapper '__iter__' of list object at 0x0000021E3A95E8C8>
>>> next(l) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'list' object is not an iterator
>>> l2 = iter(l)
>>> next(l2) 1
>>> next(l2) 2
>>> next(l2) 3
>>> next(l2) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
>>> l2 <list_iterator object at 0x0000021E3A965208>
|
迭代器(Iterators)
实现了 __iter__ 和 next 方法的对象就是迭代器, 其中, __iter__ 方法返回迭代器对象本身, next 方法返回容器的下一个元素, 在没有后续元素时抛出 StopIteration 异常。
在 Python2 中是 __next__ 方法。
可迭代对象实现一个迭代器的协议, 通过这个协议, Python 的一些内置函数和语法就能方便地访问这个对象。
下面就是一个迭代器, 它定义了 iter 和 next 方法:
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
f = Fib(100) for i in f: print(i)
0 1 1 2 3 5 8 13 21 34 55 89
print(type(f))
l = list(Fib(100)) print(l)
|
还可以通过对可迭代的对象调用内置函数 iter(), 这样就可以获得一个迭代器, 使用 next() 函数或者 next() 方法都可以获得下一个值:
>>> l = iter([1, 2, 3])
>>> next(l) 1
>>> next(l) 2
>>> l.__next__() 3
>>> l.__next__() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
|
生成器(Generator)
生成器是一种使用普通函数语法定义的迭代器。生成器和普通函数的区别是使用 yield, 而不是 return 返回值。
yield 语句一次返回一个结果, 在每个结果中间会挂起函数, 以便下次可以在离开的地方继续执行, 生成器本质上还是迭代器, 不同的是 yield 这种写法更为简洁。
def my_gen(): yield 1 yield 2
g = my_gen()
print(next(g))
1
print(g.__next__())
2
for i in my_gen(): print(i)
1 2
|
生成器表达式
我们可以使用列表推导式类似的语法创建一个生成器的表达式:
>>> g = (i for i in range(10) if i % 2) >>> g <generator object <genexpr> at 0x000002E9C3CB79E8> >>> for i in g: ... print(i) ... 1 3 5 7 9
|
协程(Coroutine)
协程和生成器很类似, 都是包含了 yield 关键字的函数, 在协程中, yield 通常处于 = 右边。
当一个函数在执行的过程中被阻塞的时候, 执行了其他的事情, 当阻塞结束之后, 可以用 next 或者 send 唤起协程。
相比于多线程, 协程的好处是在一个线程里面执行, 避免了线程之间切换带来的额外的开销, 而且多线程中, 会使用共享的资源, 往往需要加锁, 而协程不需要, 因为在协程中, 代码的执行顺序在程序中是可以预见的, 是已经定义好的, 不存在多个线程同时写某一个共享变量而导致资源抢占的问题, 也就不需要加锁了。
>>> def coroutine(): ... print('Start') ... x = yield ... print(f'Received: {x}') ...
>>> coro = coroutine()
>>> coro <generator object coroutine at 0x000002E9C3CB7C50>
>>> next(coro) Start
>>> coro.send(10) Received: 10 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
>>> coro2 = coroutine()
>>> coro2.__next__() Start >>> coro2.__next__() Received: None Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
|
下面是一个更加复杂一点的例子:
>>> def coroutine2(a): ... print(f'Start: {a}') ... b = yield a ... print(f'Received: b = {b}') ... c = yield a + b ... print(f'Received: c = {c}') ...
>>> coro = coroutine2(1)
>>> next(coro) Start: 1 1
>>> coro.send(2) Received: b = 2 3
>>> coro.send(10) Received: c = 10 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
|
协程可以将异步的编程同步化, 回调函数是一个实现异步操作的常用方法, 主线程发起一个异步的任务, 让任务自己去工作, 当任务完成之后会通过执行预先指定的回调函数来完成后续的任务, 然后返回主线程。这种模式下, 异步任务执行的过程中主线程无需等待和阻塞, 可以继续处理其它的任务。
下面是一个回调的例子:
>>> def framework(logic, callback): ... s = logic() ... print(f'[FX] logic: {s}') ... print(f'[FX] do something...') ... callback(f'async: {s}') ...
>>> def logic(): ... return 'Logic' ...
>>> def callback(s): ... print(s) ...
>>> framework(logic, callback) [FX] logic: Logic [FX] do something... async: Logic
|
上面的例子中, 每次主程序调用的时候, 都要传入一个 callback(回调函数), 使用这种回调编程的方式比较不友好, 使用协程处理则可以避免传入回调, 下面是一个使用 yield 改善程序的结构设计的例子, 让回调函数放在逻辑的最后, 中间用一个 yield 隔开, 当执行之后, send 结果, 然后在 yield 中执行, 从而实现异步:
>>> 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 ...
>>> def logic(): ... s = 'Logic' ... r = yield s ... print(r) ...
>>> framework(logic) [FX] logic: Logic [FX] do something... async: Logic
|
下面是一个使用 yield 来完成消费功能的例子:
>>> def consumer(): ... while True: ... v = yield ... print(f'consume: {v}') ...
>>> def producer(c): ... for i in range(10, 13): ... c.send(i) ...
>>> c = consumer()
>>> c.send(None)
>>> producer(c) consume: 10 consume: 11 consume: 12
>>> c.close()
|
更直观一点的例子:
>>> def consumer(): ... r = '' ... while True: ... v = yield r ... print(f'consume: {v}') ... r = f'Result: {v * 2}' ...
>>> def producer(c): ... for i in range(10, 13): ... print(f'Producing... {i}') ... r = c.send(i) ... print(f'Consumer return: {r}') ...
>>> c = consumer()
>>> c.send(None) ''
>>> producer(c) Producing... 10 consume: 10 Consumer return: Result: 20 Producing... 11 consume: 11 Consumer return: Result: 22 Producing... 12 consume: 12 Consumer return: Result: 24
|