迭代器、生成器和协程
可迭代(Iterable)
Python 中任意的对象, 只要定义了可以返回一个迭代器的 __iter__
方法, 或者支持下标索引的 __getitem__
方法, 那么它就是一个可迭代对象。
有些对象定义了 iter 和 getitem 这两种方法, 但是返回的不是一个迭代器:
>>> l = [1, 2, 3]
>>> l.__iter__ # 列表定义了 __iter__ 方法, 但是不是一个迭代器
>>> next(l)
Traceback (most recent call last):
File "", line 1, in
TypeError: 'list' object is not an iterator
>>> l2 = iter(l) # 用 iter() 把一个列表转换为可迭代对象
>>> next(l2) # 现在可以迭代了
1
>>> next(l2)
2
>>> next(l2)
3
>>> next(l2)
Traceback (most recent call last):
File "", line 1, in
StopIteration
>>> l2 # 列表类型的迭代器
迭代器(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)
# 输出: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
还可以通过对可迭代的对象调用内置函数 iter(), 这样就可以获得一个迭代器, 使用 next() 函数或者 next() 方法都可以获得下一个值:
>>> l = iter([1, 2, 3])
>>> next(l)
1
>>> next(l)
2
>>> l.__next__()
3
>>> l.__next__() # 列表只有 3 个元素, 所以第 4 次获取值就会抛出异常
Traceback (most recent call last):
File "", line 1, in
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
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 # 是一个生成器, 协程
>>> next(coro) # 启动协程
Start
>>> coro.send(10) # 10 作为 yield 的值传入
Received: 10
Traceback (most recent call last):
File "", line 1, in
StopIteration
>>> coro2 = coroutine()
>>> coro2.__next__()
Start
>>> coro2.__next__()
Received: None
Traceback (most recent call last):
File "", line 1, in
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) # 2 被赋值给 b
Received: b = 2
3 # send() 方法把一个值发送给了协程, 并且要产出一个值(a+b), 作为 send() 方法的返回值
>>> coro.send(10)
Received: c = 10
Traceback (most recent call last):
File "", line 1, in
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 # 消费者中, yield 挂起, 等待生产者通过 send() 方法传递任务
... 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