python协程学习记录(1)

本文深入探讨了Python中协程的概念及其实现方式,并通过实例展示了协程如何用于数据处理流程,包括文件监控、管道和过滤器等场景。

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

简介

最近一段时间在研究一些关于并行任务的东西,而python中对协程的支持是非常完善的,所以针对coroutine的特性和它的使用做一个总结。

coroutine的定义和使用

实际上,coroutine和generator还是很有关系的,我们来看如下的代码:

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

这部分代码看起来和generator很像,不过又不同。这里有一个line = (yield) 的语句。而在generator里,我们是需要yield value来返回值的。而这里后面根据获得的值还可以打印出来了。我们使用它们的代码如下:

if __name__ == '__main__':  
    g = grep("python")  
    g.next()  
    g.send("Yeah, but no, but yeah, but no")  
    g.send("A series of tubes")  
    g.send("python generators rock!")

这里,我们定义了方法grep,然后调用一个send方法。代码执行的输出结果如下:

Looking for python  
python generators rock! 

结合前面代码的部分,看起来好像是yield部分后面会针对包含有python这个串的字符进行处理。而没有的则不会处理。看来这个yield像是有什么玄机,看起来不简单。
实际上,我们在使用line = (yield)这部分就是定义了一个coroutine。coroutine是什么呢?coroutine可以说是一种实现协作式编程的手法。它可以设置有多个入口点和恢复执行的点,可以实现一些执行流程的转移。这部分概念看起来有点难懂。我们以前面的这部分代码为例来分析一下想关的概念。
在我们代码中,定义的yield这个部分相当于等待接收数据。所以在没有数据到来的时候,它就相当于被阻塞,等在那里。而为了触发这个部分,我们在使用的代码里首先用g.next()来初始化它。然后通过g.send(“”)方法将数据发送给它。这样,yield返回的就是send方法里带的参数了。然后我们可以接着在循环部分来处理它。这样看来,coroutine更多的是一个数据消费者的角色。每次都是等数据过来,来了之后就通过yield部分返回,然后处理。否则就等在那里。

coroutine的几个应用

因为coroutine相当于一个数据的消费者,我们可以做一种这样的应用。首先是一个生产者将一些数据准备好,然后将数据发送给一个coroutine来处理。我们来看一个文件处理的示例:

import time  
def follow(thefile, target):  
    thefile.seek(0,2)      # Go to the end of the file  
    while True:  
         line = thefile.readline()  
         if not line:  
             time.sleep(0.1)    # Sleep briefly  
             continue  
         target.send(line)  

这里是一个读取文件,然后将文件内容通过target发送到target来处理的过程。所以我们这里要做的就是将target作为一个参数传入给follow方法。当然,target在这里必须是一个coroutine,它来接收和处理这些数据。target的定义如下:

@coroutine  
def printer():  
    while True:  
         line = (yield)  
         print line,  

这里用到了yield,然后就可以直接将接收到的数据打印出来了。当然,还有一个值得注意的地方是用到了一个@coroutine的decorator。因为在每次使用coroutine之前我们需要调用一次target.next()或者target.send(None)来初始化它。这样我们使用的时候很容易忘记这一步,一种办法就是定义好一个这样的decorator,然后每次将这个decorator加上就保证这一步被执行了。@coroutine decorator的定义如下:

def coroutine(func):  
    def start(*args,**kwargs):  
        cr = func(*args,**kwargs)  
        cr.next()  
        return cr  
    return start  

对于这部分decorator理解如果有问题的话可以参考我decorator相关的这篇文章。我们最终使用前面这部分的代码如下:

if __name__ == '__main__':  
    f = open("access-log")  
    follow(f,printer())  

当然,这个示例主要讲的是使用一个coroutine来处理一个传递过来的消息。如果我们要构造类似于pipeline的东西,可以将一个coroutine同时当作数据处理的部分,也可以当作数据传递的部分,比如看如下的代码:

@coroutine  
def grep(pattern,target):  
    while True:  
        line = (yield)           # Receive a line  
        if pattern in line:  
            target.send(line)    # Send to next stage 

这里我们定义的grep方法在接收到数据之后,相当于做了一个判断,如果pattern在传入的line中间,则将这个数据传递给下一个coroutine处理。这就实现了一个pipeline的雏形。当然,除了这种传输的示例,我们也可以将消息传递给多个coroutine。这些示例我们可以参考后面的一些资料去做详细的分析。

总结

coroutine是一个实现多个任务之间互相切换的手段,它相当于一种将一个当前执行的结果传递给另外一个过程。和generator的使用过程比起来,它更像是一种“推”模式。因为我们要使用一个coroutine的时候,必然是需要有其它的过程send数据过来。因为yield的过程有点类似于一个操作系统里中断的概念,它相当于将一个进程的当前执行过程暂停,然后跳转到另外一个过程。这种过程和我们传统通过栈实现的子过程调用很不一样,所以表面上理解起来还是有点困难。

参考材料

http://en.wikipedia.org/wiki/Coroutine
http://www.dabeaz.com/coroutines/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值