Python中协程(Coroutine)的使用和注意点

本文介绍了Python中协程的优势,包括分片计算、手动暂停和恢复功能等。通过具体实例展示了如何利用协程提高代码的可读性、可维护性和可扩展性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 协程的优势

在Lua中有协程,在Python里也有协程(Coroutine)。那为什么会需要协程,协程的好处有哪些?

1. 分片计算(sliced calculating)

我们可以把一个原本很复杂的计算分成若干断,每次算一小段,然后每次获得一小部分的结果。这样就会降低卡顿现象的出现概率。

2. 手动暂停和恢复

这个好处就是,有一些东西我可以做到一半,当需要其他函数支持的时候,可以让那些函数先执行,结果返回后,再继续执行这些东西。

3. 可读性,可维护性,可扩展性

例如从经典的“消费者和生产者”模型的角度来看(生产者产生数据后,在其他地方消费它),使用协程编写的代码可读性更高。当然如果代码的可读性更高,那个他的可维护性和可扩展性也就更好。

那Python是如何支持协程(Coroutine)的呢?

1. 使用协程和注意点

举个经典的栗子–素数筛(Sieve of primes):给定一个较大值n,寻找1~n中的所有素数,并把它打印出来。

判断一个数m是否是素数,需要花费O(√n)时间。当然是遍历并判断的话,需要花费O(n√n)时间,太慢了,因此有人就提出了素数筛的方法,如下代码:

def prime_sieve(n):
    flags = [True] * n
    flags[0] = flags[1] = False

    for i in xrange(2, n):
        if flags[i]:
            yield i

            for j in xrange(2, (n - 1) / i + 1):
                flags[i * j] = False

for p in prime_sieve(100):
    print p

前面的prime_sieve方法是生产者-把所有的素保存下来,后面的for-loop是把消费者-保存下来的素数打印出来。

生产者实现也很简单,首先生成一个bool列表表示该index的数是否是素数;显然0,1不是素数;接下来判断2<=i<=n的数,如果为素数(True),则yield抛出,并把i的2倍,3倍,…,(n-1)/i+1倍都设为不是素数(False)。这就是素数筛的原理,花费时间为O(n)

而上面就是一个典型的Coroutine的使用:

在代码的消费者的for-loop中,调用prime_sieve函数;在函数里,当使用关键字yield时候,函数就会暂停,并且把素数i抛出来。当loop再次调用下一次prime_sieve时,函数就会从yield语句(抛出来的语句)下面一句恢复堆栈并且向下执行。

那上面的prime_sieveprime_sieve(100)分别是什么东西呢?我们把他打印出来:

print prime_sieve(100) # >> <generator object at Ox00DB4EE0>
print prime_sieve # >> <function prime_sieve at 0x00DB6830>

因此判断一个对象是否是generator object,就是看函数内部是否有yield语句。
换句话说,如果一个函数中存在yield语句,那么Python就会认为这个函数调用就是一个generator object

那如何使用yield语句,帮助我们实现第1节中的优势功能呢?请看下一个例子,实现了将list或是tuple中的每一个元素递归打印出来:

def item_iterator(embed_list):
    for item in embed_list:
        if isinstance(item, (tuple, list)):
            item_iterator(item)
        else:
            yield item

lst = (0, (1, 2), (3, (4, 5)))
for item in item_iterator(lst):
    print item

通过递归迭代打印,期望在代码中输出 "0 1 2 3 4 5",然而当你运行这段代码时,发现是输出一个"0"

问题出在item_iterator的函数中存在yield语句,所以他是一个generator object,所以当在第4行递归使用item_iterator(item)时,它并不是一个函数的递归调用,而是一个generator object,所以正确item_iterator的写法是这样的:

def item_iterator(embed_list):
    for item in embed_list:
        if isinstance(item, (tuple, list)):
            for i in item_iterator(item)
                yield i
        else:
            yield item

2. 总结

总之,当函数里面有个yield,那个它就是一个generator object,不要把这个generator丢掉,如果要使用递归调用的话,切记再使用一个for-loop,这样才会正确。

### Python 协程 Coroutine 使用教程与常见问题 #### 一、协程简介 协程是一种并发编程技术,它允许程序在执行过程中暂停恢复。协程可以看作一种特殊的函数,能够暂停其执行去运行其他的任务,并能在之后返回到暂停处继续执行。相较于线程或进程而言,协程拥有更低的资源消耗以及更高的执行效率[^4]。 #### 二、使用 async await 定义协程 `async` `await` 是 Python 中用于定义调用协程的关键字。这两个关键字使得编写异步代码变得更加简洁直观,有助于处理并发任务。下面是一个简单的例子来展示如何创建并启动一个基本的协程: ```python import asyncio async def say_after(delay, what): await asyncio.sleep(delay) print(what) async def main(): task1 = say_after(1, 'hello') task2 = say_after(2, 'world') # Wait until both tasks are completed (should take around 2 seconds.) await task1 await task2 # Run the event loop to execute coroutines. asyncio.run(main()) ``` 这段代码展示了两个延迟打印语句的任务被安排在一个事件循环里依次完成;其中每个任务都会等待指定的时间间隔后再输出相应的内容[^1]。 #### 三、利用 asyncio 编写并发异步代码 `asyncio` 模块提供了丰富的工具支持开发者构建复杂的异步应用程序。除了上述提到的基础功能外,还包含了诸如队列、锁机制等功能组件帮助管理多任务间的协作关系。这里给出一段更加复杂一的例子说明如何同时发起多个 HTTP 请求而不阻塞主线程的工作流程: ```python import aiohttp import asyncio async def fetch(session, url): async with session.get(url) as response: return await response.text() async def main(urls): async with aiohttp.ClientSession() as session: results = await asyncio.gather(*[fetch(session, url) for url in urls]) for result in results: print(result[:100]) # Print first 100 characters of each page. urls = ["https://example.com", "https://www.python.org"] asyncio.run(main(urls)) ``` 此段脚本会向给定 URL 列表发送 GET 请求并将接收到的数据部分显示出来。值得注意的是整个过程是在同一个线程内高效地完成了多项I/O操作而无需担心传统同步模式下可能产生的性能瓶颈问题[^2]。 #### 四、Gevent 库介绍及其应用场景 对于那些希望简化开发工作量又不想深入研究底层细节的人来说,`gevent` 可能是个不错的选择。该库基于 `greenlet` 构建而成并通过所谓的“猴子补丁”实现了透明化控制流转移的效果——即当某个绿色线程处于休眠状态时操作系统将会自动切换至下一个可运行实例上继续推进整体进度直至所有待办事项都被妥善处置完毕为止。特别是在面对大量短时间内的高频率交互请求(比如Web服务器端口监听)或是长时间占用外部资源的操作场合之下往往可以获得较为理想的吞吐率表现[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值