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

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,这样才会正确。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
协程Coroutine)是一种用户态的轻量级线程,它可以在单个线程实现多任务并发处理。Python协程通过生成器(generator)实现,使用yield语句来实现协程的暂停和恢复操作。 在Python 3.5之后,Python引入了async/await关键字,使得协程使用更加方便和简洁。 下面是一个使用yield实现协程的示例: ```python def coroutine(): print("coroutine started") while True: value = yield print("Received value: ", value) c = coroutine() next(c) # 启动协程 c.send(1) # 发送值,并打印接收到的值 c.send(2) ``` 输出: ``` coroutine started Received value: 1 Received value: 2 ``` 在上面的代码使用yield语句实现了协程的暂停和恢复操作。在调用`c.send()`方法时,会将值发送给协程,并从yield语句处恢复协程的执行。协程会处理接收到的值,并在下一个yield语句处暂停,等待下一次发送。 除了使用yield语句来实现协程外,Python 3.5之后还可以使用async/await关键字来定义协程使用async/await关键字定义的协程更加简洁和易于理解。下面是一个使用async/await关键字实现的协程示例: ```python async def coroutine(): print("coroutine started") while True: value = await asyncio.sleep(1) print("Received value: ", value) asyncio.run(coroutine()) ``` 在上面的代码使用async/await关键字定义了一个协程使用`asyncio.sleep()`函数来实现协程的暂停操作,并在下一次事件循环时恢复协程的执行。使用`asyncio.run()`函数来运行协程。 总的来说,协程是一种非常有用的并发编程技术,可以在单个线程实现高并发的处理。在Python,可以使用生成器和async/await关键字来实现协程

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值