Python Coroutine 初探

从Python2.5开始

首先引用PEP342开篇的一段话

This PEP proposes some enhancements to the API and syntax of generators, to make them usable as simple coroutines.

在Python2.x,我们可以用Generator来实现Coroutine的功能。

def coroutine(func):
    def wrapper(*args, **kwargs):
        cr = func(*args, **kwargs)    
        try:
            cr.next()
        except StopIteration: pass
        return cr
    return wrapper

上面是一个coroutine的修饰器,如下是使用此修饰器的方法。

@coroutine
def grep(pattern):
     print "Looking for %s" % pattern
     while True:
     line = (yield)
     if pattern in line:
     print line,

 g = grep("hello")
 g.send("hello world")
 g.send("test hello")
 g.send("test world")

其实python在编译grep这段代码的时候,发现有yield关键字,func_code的co_flags会存在CO_GENERATOR标志位,因此在PyEval_EvalCodeEx执行的过程中会返回一个generator实例,而不是去执行这个函数的func_code。generator实例中会保存对应的PyFrameObject。

PyObject *
PyGen_New(PyFrameObject *f)
{
    PyGenObject *gen = PyObject_GC_New(PyGenObject, &PyGen_Type);
    ... ...
    gen->gi_frame = f;
    Py_INCREF(f->f_code);
    gen->gi_code = (PyObject *)(f->f_code);
    gen->gi_running = 0;
    gen->gi_weakreflist = NULL;
    _PyObject_GC_TRACK(gen);
    return (PyObject *)gen;
}

在generator的send方法中会继续运行此时保存的PyFrameObject。

static PyObject *
gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
{
    PyThreadState *tstate = PyThreadState_GET();
    PyFrameObject *f = gen->gi_frame;
    PyObject *result;
    ... ...
    result = PyEval_EvalFrameEx(f, exc);
    ... ...
    return result
}

Python3.3的yield from

Python3.3针对Generator加入了新的语法yield from,个人感觉新的语法可以让某些功能的实现更新简洁(其实就是少写点代码啊,,,),但真正本质上并没有改善Generator。这篇文章对yield from语法做了一定的解释。

Python3.5 async def

Python3.5引入了async def语法,定义一个coroutine。

import asyncio

async def compute(x, y):
    print("Compute %s + %s ..." % (x, y))
    await asyncio.sleep(1.0)
    return x + y

async def print_sum(x, y):
    result = await compute(x, y)
    print("%s + %s = %s" % (x, y, result))

loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(1, 2))
loop.close()

当async def函数里面调用await的时候,这个coroutine将会被挂起(Suspended)。但看Python3.5的源码后发现,coroutine的内部实现和generator的内部实现基本相同。

# genobject.c
PyTypeObject PyCoro_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "coroutine",                                /* tp_name */
    ...
    coro_methods,                               /* tp_methods */
    ...
};

上面是coroutine的PyTypeObject的定义,其实一开始的示例代码中print_sum函数返回的正是一个coroutine实例(是不是跟generator很像呢?!)。我们在看一下coro_methods的定义。

static PyMethodDef coro_methods[] = {
    {"send",(PyCFunction)_PyGen_Send, METH_O, coro_send_doc},
    {"throw",(PyCFunction)gen_throw, METH_VARARGS, coro_throw_doc},
    {"close",(PyCFunction)gen_close, METH_NOARGS, coro_close_doc},
    {NULL, NULL}        /* Sentinel */
};

send方法的实现就是_PyGen_Send,和generator的实现一模一样,await后面可以紧跟一个新的coroutine实例,这个功能则归功于Python3.3开始出现的yield from语法。

references:

  1. PEP342 - https://www.python.org/dev/peps/pep-0342/
  2. PEP380 - https://docs.python.org/3/whatsnew/3.3.html#pep-380
  3. A Curious Course on Coroutines and Concurrency - http://www.dabeaz.com/coroutines/Coroutines.pdf