协程

Python的协程很像生成器,但并不是生成数据,大多数时候扮演了数据消费者的作用。换句话说,协程是一个在每次使用send方法发送数据后就会被唤醒的函数。

协程的技巧是将“yield”关键字写在表达式的右边。下面是一个打印出所发送的值的协程例子:

  1. def coroutine():
  2. print('My coroutine')
  3. while True:
  4. val = yield
  5. print('Got', val)
  6. >>> co = coroutine()
  7. >>> next(co)
  8. My coroutine
  9. >>> co.send(1)
  10. Got 1
  11. >>> co.send(2)
  12. Got 2
  13. >>> co.send(3)
  14. Got 3

首先要调用“next”来将协程推进。你可以看到它执行了一个打印。最终,函数进行到“yield”表达式了,然后就会等待唤醒。之后,每次有值被发送过来,协程就会从“yield”处唤醒,将值复制给val并打印出它。
使用close()方法可以关闭这个协程。

  1. >>> co.close()
  2. >>> co.send(4)
  3. Traceback (most recent call last):
  4. File "<stdin>", line 1, in <module>
  5. StopIteration

协程部分练习

  1. 创建一个名为“square”的协程,它会打印出所发送的值的平方。
  2. 创建一个名为“minimize”的协程,它会保存并打印出所发送的值中最小的一个值。

管道

协程可以用在部署数据管道中,其中一个协程会发送数据到下一个在数据管道中的协程。协程使用send()方法来将数据压入管道。

coroutine_pipeline

这是一个小型管道,值会从生产者协程发送到消费者协程打印出来:

  1. def producer(consumer):
  2. print("Producer ready")
  3. while True:
  4. val = yield
  5. consumer.send(val * val)
  6. def consumer():
  7. print("Consumer ready")
  8. while True:
  9. val = yield
  10. print('Consumer got', val)

正如上面所说,在发送任何数据前调用“next”是非常重要的一个步骤。

  1. >>> cons = consumer()
  2. >>> prod = producer(cons)
  3. >>> next(prod)
  4. Producer ready
  5. >>> next(cons)
  6. Consumer ready
  7. >>> prod.send(1)
  8. Consumer got 1
  9. >>> prod.send(2)
  10. Consumer got 4
  11. >>> prod.send(3)
  12. Consumer got 9

同样的,对于协程来说,数据可以被发送到不同的地方。下面的例子是部署了两个消费者,第一个它只会打印0-10之间的内容,第二个则是10-20。

  1. def producer(consumers):
  2. print("Producer ready")
  3. try:
  4. while True:
  5. val = yield
  6. for consumer in consumers:
  7. consumer.send(val * val)
  8. except GeneratorExit:
  9. for consumer in consumers:
  10. consumer.close()
  11. def consumer(name, low, high):
  12. print("%s ready" % name)
  13. try:
  14. while True:
  15. val = yield
  16. if low < val < high:
  17. print('%s got' % name, val)
  18. except GeneratorExit:
  19. print("%s closed" % name)

不要忘了调用下“next”呦。

  1. >>> con1 = consumer('Consumer 1', 00, 10)
  2. >>> con2 = consumer('Consumer 2', 10, 20)
  3. >>> prod = producer([con1, con2])
  4. >>> next(prod)
  5. Producer ready
  6. >>> next(con1)
  7. Consumer 1 ready
  8. >>> next(con2)
  9. Consumer 2 ready
  10. >>> prod.send(1)
  11. Consumer 1 got 1
  12. >>> prod.send(2)
  13. Consumer 1 got 4
  14. >>> prod.send(3)
  15. Consumer 1 got 9
  16. >>> prod.send(4)
  17. Consumer 2 got 16
  18. >>> prod.close()
  19. Consumer 1 closed
  20. Consumer 2 closed

数据被发送到所有的消费者中,但只有第二个执行了打印命令。注意这里使用的“GeneratorExit”异常。一般都会用捕获异常的方式来通知下游协程这个管道没有用了,赶紧关闭了吧。

consumers_pipline

协程部分练习

  1. 部署一个生产者-消费者管道,生产者会将值的平方发送给两个消费者。其中一个会储存并打印出所发送的值中最小的一个,另一个则是最大的一个。

  2. 部署一个生产者-消费者管道,生产者会将值的平方发送给两个消费者,一次只发送给其中的一个。第一个值会发送给消费者1号,第二个则会发送给消费者2号,第三个又发送给消费者1号…. 关闭生产者时会强制让消费者打印出它们所接收到的值的列表。