Python Coroutine 初探

本文介绍了Python协程的发展历程,从Python 2.5引入的基本概念到Python 3.3新增的yieldfrom语法,再到Python 3.5的async/await语法。详细解析了协程在Python中的实现原理及应用。

原文链接:http://whosemario.github.io/2015/11/22/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
### Python 协程(Coroutines)的理解与使用 Python 中的协程是一种特殊的函数,它允许在执行过程中暂停并在之后恢复。这种特性非常适合处理异步编程、并发任务和数据流控制等场景。从本质上讲,协程通过 `async/await` 语法实现,使得编写异步代码更加直观和简洁。 #### 定义协程函数 使用 `async def` 关键字可以定义一个协程函数。当调用该函数时,并不会立即执行其内部逻辑,而是返回一个协程对象。要实际运行该协程,需要将其放入事件循环中并等待其完成[^1]。 ```python async def my_coroutine(): print("Coroutine started") await asyncio.sleep(1) print("Coroutine finished") ``` #### 启动协程 为了运行协程,必须有一个事件循环。可以通过 `asyncio.run()` 函数来启动主协程,这会自动创建一个新的事件循环并管理其生命周期。 ```python import asyncio asyncio.run(my_coroutine()) ``` #### 并发执行多个协程 协程的一个重要用途是并发执行多个任务。使用 `asyncio.gather()` 可以同时启动多个协程,并按顺序等待它们全部完成。 ```python async def task(name, delay): print(f"Task {name} started") await asyncio.sleep(delay) print(f"Task {name} finished") async def main(): await asyncio.gather( task("A", 2), task("B", 1), task("C", 3) ) asyncio.run(main()) ``` #### 使用 `await` 表达式 在协程内部,可以使用 `await` 来等待另一个协程或异步操作的结果。如果表达式是一个协程,则事件循环会在该协程完成后继续执行当前协程[^1]。 ```python async def get_data(): await asyncio.sleep(2) return {"data": 42} async def main(): result = await get_data() print(result) asyncio.run(main()) ``` #### 异步 I/O 操作示例 协程非常适合用于网络请求、文件读写等 I/O 密集型操作。以下是一个简单的 HTTP 请求示例,展示了如何利用 `aiohttp` 库进行异步网络通信[^1]。 ```python import aiohttp import asyncio async def fetch(session, url): async with session.get(url) as response: return await response.text() async def main(): async with aiohttp.ClientSession() as session: html = await fetch(session, 'https://example.com') print(html[:100]) # 打印前100个字符 asyncio.run(main()) ``` #### 错误处理 协程中的异常处理与普通函数类似,但需要注意的是,协程的错误通常是在 `await` 表达式时抛出的。因此,在 `try/except` 块中使用 `await` 是捕获协程错误的标准方法[^1]。 ```python async def faulty(): raise Exception("Something went wrong") async def main(): try: await faulty() except Exception as e: print(f"Caught error: {e}") asyncio.run(main()) ``` #### 协程与生成器的区别 虽然早期版本的 Python 曾经尝试用生成器模拟协程行为,但从 Python 3.5 开始引入了原生支持的 `async/await` 机制。相比传统的基于生成器的协程,新的语法更易于理解和维护,同时也提供了更好的性能表现。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值