Skip to content

Latest commit

 

History

History
179 lines (132 loc) · 10.9 KB

第十六章-协程.md

File metadata and controls

179 lines (132 loc) · 10.9 KB

CHAPTER 16 Coroutines

If Python books are any guide, [coroutines are] the most poorly documented, obscure, and apparently useless feature of Python. — David Beazley Python author

We find two main senses for the verb “to yield” in dictionaries: to produce or to give way. Both senses apply in Python when we use the yield keyword in a generator. A line such as yield item produces a value that is received by the caller of next(...), and it also gives way, suspending the execution of the generator so that the caller may proceed until it’s ready to consume another value by invoking next() again. The caller pulls values from the generator.

A coroutine is syntactically like a generator: just a function with the yield keyword in its body. However, in a coroutine, yield usually appears on the right side of an expres‐ sion (e.g., datum = yield), and it may or may not produce a value—if there is no expression after the yield keyword, the generator yields None. The coroutine may re‐ ceive data from the caller, which uses .send(datum) instead of next(...) to feed the coroutine. Usually, the caller pushes values into the coroutine.

协程在语法上类似于生成器:无非是一个在语句主体中包含yield关键字的函数。不过,在协程中,yield通常出现在表达式的右边(比如,datum = yield),而且如果在yield关键字之后没有表达式,它可以产生值也可以不产生值——生成器生成None。协从调用者接受数据,调用者使用send(datum)而不是next(...)反馈给协程。通常,调用者会把值放进协程中。

It is even possible that no data goes in or out through the yield keyword. Regardless of the flow of data, yield is a control flow device that can be used to implement cooperative multitasking: each coroutine yields control to a central scheduler so that other corou‐ tines can be activated.

When you start thinking of yield primarily in terms of control flow, you have the mindset to understand coroutines.

Python coroutines are the product of a series of enhancements to the humble generator functions we’ve seen so far in the book. Following the evolution of coroutines in Python helps understand their features in stages of increasing functionality and complexity.

Python协程

After a brief overview of how generators were enable to act as a coroutine, we jump to the core of the chapter. Then we’ll see:

在一个生成器如何能够充当协程的简短概要之后,我们走进了本章的核心。接下来我们可以看到:

  • The behavior and states of a generator operating as a coroutine

  • Priming a coroutine automatically with a decorator

  • How the caller can control a coroutine through the .close() and .throw(...) methods of the generator object

  • How coroutines can return values upon termination

  • Usage and semantics of the new yield from syntax

  • A use case: coroutines for managing concurrent activities in a simulation

  • 当作协程操作的生成器行为和状态

  • 使用装饰器自动地启用一个协程

  • 调用者如何能够通过生成器对象的.close()和.throw(...)方法控制协程

  • 协程如何能在终止之前返回值

  • 用例:在模拟环境中用协程管理并发活动

How Coroutines Evolved from Generators

The infrastructure for coroutines appeared in PEP 342 — Coroutines via Enhanced Generators, implemented in Python 2.5 (2006): since then, the yield keyword can be used in an expression, and the .send(value) method was added to the generator API. Using .send(...), the caller of the generator can post data that then becomes the value of the yield expression inside the generator function. This allows a generator to be used as a coroutine: a procedure that collaborates with the caller, yielding and receiving values from the caller.

协程的基本架构出现在PEP 342中——由生成器加强过的协程,在Python2.5(2006年)中得以实现:从那时候起,yield关键字便可用在表达式中,同时.send(value)方法被加入到了生成器API。

In addition to .send(...), PEP 342 also added .throw(...) and .close() methods that respectively allow the caller to throw an exception to be handled inside the generator, and to terminate it. These features are covered in the next section and in “Coroutine Termination and Exception Handling” on page 471.

除了.send(...)之外,PEP 342增加了.throw(...)和.close()方法,已分别允许调用者在生成器内部抛出一个异常,并终止程序。这些功能将在下一节和471页上的“协程终止与异常处理”讲到。

The latest evolutionary step for coroutines came with PEP 380 - Syntax for Delegating to a Subgenerator, implemented in Python 3.3 (2012). PEP 380 made two syntax changes to generator functions, to make them more useful as coroutines:

协程的最新演化路径随着PEP 380到来——即,在Python3.3(2012年)实现的分派子生成器语法。PEP 380对生成器做了两个语法上的改变,以便

  • A generator can now return a value; previously, providing a value to the return statement inside a generator raised a SyntaxError.

  • The yield from syntax enables complex generators to be refactored into smaller, nested generators while avoiding a lot of boilerplate code previously required for a generator to delegate to subgenerators.

  • 现在生成器可以返回一个值;

  • yield from语法能够让复杂的生成器重构到更小,

These latest changes will be addressed in “Returning a Value from a Coroutine” on page 475 and “Using yield from” on page 477.

这些最新的改变声明在475页中的“从协程中返回一个值”,以及477页上的“使用yield from”。

Let’s follow the established tradition of Fluent Python and start with some very basic facts and examples, then move into increasingly mind-bending features.

我们遵照Fluent Python所建立的传统,从一些非常基本的样例开始,然后一步步接近那些令人难以理解的特性。

Basic Behavior of a Generator Used as a Coroutine

Example 16-1 illustrates the behavior of a coroutine.
例子16-1 阐明了协程的行为。

Example 16-1. Simplest possible demonstration of coroutine in action
例子16-1。

>>> def simple_coroutine(): # 1
...     print('-> coroutine started')
...     x = yield # 2
...     print('-> coroutine received:', x)
...
>>> my_coro = simple_coroutine()
>>> my_coro # 3
<generator object simple_coroutine at 0x100c2be10> 
>>> next(my_coro) # 4
-> coroutine started
>>> my_coro.send(42) # 5
-> coroutine received: 42
Traceback (most recent call last): # 6
    ...
    StopIteration
  1. A coroutine is defined as a generator function: with yield in its body.

  2. yield is used in an expression; when the coroutine is designed just to receive data from the client it yields None—this is implicit because there is no expression to the right of the yield keyword.

  3. As usual with generators, you call the function to get a generator object back.

  4. The first call is next(...) because the generator hasn’t started so it’s not waiting in a yield and we can’t send it any data initially.

  5. This call makes the yield in the coroutine body evaluate to 42; now the coroutine resumes and runs until the next yield or termination.

  6. In this case, control flows off the end of the coroutine body, which prompts the generator machinery to raise StopIteration, as usual.

  7. 协程像生成器函数那样被定义:在其主体内使用yield

  8. yield

A coroutine can be in one of four states. You can determine the current state using the inspect.getgeneratorstate(...) function, which returns one of these strings:

协程可以是四种状态中的一种。

'GEN_CREATED' Waiting to start execution. 'GEN_RUNNING' Currently being executed by the interpreter.[1]

[1. You’ll only see this state in a multithreaded application—or if the generator object calls getgenerator state on itself, which is not useful.]

'GEN_SUSPENDED' Currently suspended at a yield expression. 'GEN_CLOSED' Execution has completed.

Because the argument to the send method will become the value of the pending yield expression, it follows that you can only make a call like my_coro.send(42) if the coro‐ utine is currently suspended. But that’s not the case if the coroutine has never been activated—when its state is 'GEN_CREATED'. That’s why the first activation of a coroutine is always done with next(my_coro)—you can also call my_coro.send(None), and the effect is the same.

If you create a coroutine object and immediately try to send it a value that is not None, this is what happens:

>>> my_coro = simple_coroutine() 
>>> my_coro.send(1729)
Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can't send non-None value to a just-started generator

Note the error message: it’s quite clear.

The initial call next(my_coro) is often described as “priming” the coroutine (i.e., ad‐ vancing it to the first yield to make it ready for use as a live coroutine).

To get a better feel for the behavior of a coroutine, an example that yields more than once is useful. See Example 16-2.

为了更好的感受协程的行为,有一个yield不止一次的例子就显得很有用了。参见例子16-2.

Example 16-2. A coroutine that yields twice

例子16-2. yield两次的协程

>>> def simple_coro2(a):
...     print('-> Started: a =', a)
...     b = yield a
...     print('-> Received: b =', b)
...     c = yield a + b
...     print('-> Received: c =', c)
...
>>> my_coro2 = simple_coro2(14)
>>> from inspect import getgeneratorstate 
>>> getgeneratorstate(my_coro2) # 1
'GEN_CREATED'
>>> next(my_coro2) # 2
-> Started: a = 14
14
>>> getgeneratorstate(my_coro2)  # 3
'GEN_SUSPENDED'
>>> my_coro2.send(28) # 4
-> Received: b = 28
42
>>> my_coro2.send(99) # 5
-> Received: c = 99
Traceback (most recent call last):
    File "<stdin>", line 1, in <module> StopIteration
>>> getgeneratorstate(my_coro2)  # 6
'GEN_CLOSED'
  1. inspect.getgeneratorstate reports GEN_CREATED (i.e., the coroutine has not started).
  2. Advance coroutine to first yield, printing -> Started: a = 14 message then yielding value of a and suspending to wait for value to be assigned to b.
  3. getgeneratorstate reports GEN_SUSPENDED (i.e., the coroutine is paused at a yield expression).
  4. Send number 28 to suspended coroutine; the yield expression evaluates to 28 and that number is bound to b. The -> Received: b = 28 message is displayed, the value of a + b is yielded (42), and the coroutine is suspended waiting for the value to be assigned to c.
  5. Send number 99 to suspended coroutine; the yield expression evaluates to 99 the number is bound to c. The -> Received: c = 99 message is displayed, then the coroutine terminates, causing the generator object to raise StopIteration.
  6. getgeneratorstate reports GEN_CLOSED (i.e., the coroutine execution has completed).