从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:
- PEP342 - https://www.python.org/dev/peps/pep-0342/
- PEP380 - https://docs.python.org/3/whatsnew/3.3.html#pep-380
- A Curious Course on Coroutines and Concurrency - http://www.dabeaz.com/coroutines/Coroutines.pdf