Python学习总结笔记(7)-- 生成器与协程

来源:互联网 发布:java输入输出流详解 编辑:程序博客网 时间:2024/05/24 06:19

在调用普通函数时,程序会中断调用代码运行,切换到调用函数的第一行代码开始执行,到return结束。并且将控制还给调用者,被调用函数状态结束并清空(局部变量等)。如果再次调用该函数,我们需要一切从头重新来过。生成器(协程)就是一种不同于这种模式的新方式,具有非常强大的功能,可以简化我们的代码逻辑,更可以降低内存消耗,提高代码质量与效率。

0x01 问题提出

来看下面一个例子。我们需要写一个函数,用来提取一个序列中的全部素数。输入参数是待提取的序列,返回提取的素数序列。

#/usr/bin/env python#coding:utf-8__author__ = 'kikay'import math#判断一个数值是否是素数def isPrime(iNum):    if iNum>1:        if iNum==2:            return True        if iNum%2==0:            return False        for i in range(3,int(math.sqrt(iNum)+1),2):            if iNum%i==0:                return False        return True    return False#列表生成方法def GetByList(iList):    res=list()    for i in iList:        if isPrime(i):            res.append(i)    return resif __name__=='__main__':    L=xrange(0,100)    res=GetByList(L)    print type(res)    for i in res:        print i,

输出结果:

<type 'list'>2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

0x02 生成器

上面的GetByList函数我们可以用生成器进行优化(优化代码以及内存空间),修改后完整代码如下:

#/usr/bin/env python#coding:utf-8__author__ = 'kikay'import math#判断一个数值是否是素数def isPrime(iNum):    if iNum>1:        if iNum==2:            return True        if iNum%2==0:            return False        for i in range(3,int(math.sqrt(iNum)+1),2):            if iNum%i==0:                return False        return True    return False#列表生成方法def GetByList(iList):    res=list()    for i in iList:        if isPrime(i):            res.append(i)    return res#生成器def GetByGenerator(iList):    return (i for i in iList if isPrime(i))if __name__=='__main__':    L=range(0,100)    #res=GetByList(L)    res=GetByGenerator(L)    print type(res)    for i in res:        print i,

运行结果:

<type 'generator'>2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

用GetByGenerator代替了函数GetByList,不仅代码更加简单,而且节约了内存。上面的改进体现在把返回的结果从List列表对象改成了Generator生成器对象,但是问题来了,我们需要提取小于某个数值(end)的全部素数,但是end的值不确定,可能非常大。修改后的代码如下:

#/usr/bin/env python#coding:utf-8__author__ = 'kikay'import math#判断一个数值是否是素数def isPrime(iNum):    if iNum>1:        if iNum==2:            return True        if iNum%2==0:            return False        for i in range(3,int(math.sqrt(iNum)+1),2):            if iNum%i==0:                return False        return True    return False#列表生成方法def GetByList(iList):    res=list()    for i in iList:        if isPrime(i):            res.append(i)    return res#生成器def GetByGenerator(iList):    return (i for i in iList if isPrime(i))#生成器(含条件)def GetByGenerator(iList,iEnd):    return (i for i in iList if isPrime(i) and iEnd>i)if __name__=='__main__':    end=100    L=xrange(0,end)    #res=GetByList(L)    res=GetByGenerator(L,end)    print type(res)    for i in res:        print i,

运行结果:

<type 'generator'>2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

0x03 生成器函数

上面较好地解决了提出的问题,但是我们可以看到,函数输入参数中还是带了1个List对象(或者Generator对象),计算完毕后返回了1个Generator对象。那么有没有可能不需要输入Generator对象呢?答案是可以的,我们可以进一步定义Generator函数,改写后的代码如下:

#/usr/bin/env python#coding:utf-8__author__ = 'kikay'import math#判断一个数值是否是素数def isPrime(iNum):    if iNum>1:        if iNum==2:            return True        if iNum%2==0:            return False        for i in range(3,int(math.sqrt(iNum)+1),2):            if iNum%i==0:                return False        return True    return False#列表生成方法def GetByList(iList):    res=list()    for i in iList:        if isPrime(i):            res.append(i)    return res#生成器def GetByGenerator(iList):    return (i for i in iList if isPrime(i))#生成器(含条件)def GetByGenerator(iList,iEnd):    return (i for i in iList if isPrime(i) and iEnd>i)#生成器函数def GetByGenerator_Fun(iEnd):    i=0    while i<iEnd:        if isPrime(i):            yield i        i+=1if __name__=='__main__':    #模拟最大值    end=100    #L=xrange(0,end)    #res=GetByList(L)    #res=GetByGenerator(L,end)    #print type(res)    f=GetByGenerator_Fun(end)    print type(f)    for i in f:        print i

运行结果:

<type 'generator'>2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

上面有一处yield。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行(把函数改成generator后,基本上不会用next()来调用它,而是直接使用for循环来迭代,见上面的例子)。

0x04 协程

前面我们演示了关于素数的例子。现在我们改下需求:找出比某个数的等比数列大的最小素数(比如num=10,我们要生成比“10,100,1000,10000,…”大的最小素数)。先看下用协程思路来完成的代码:

#/usr/bin/env python#coding:utf-8__author__ = 'kikay'import math#判断一个数值是否是素数def isPrime(iNum):    if iNum>1:        if iNum==2:            return True        if iNum%2==0:            return False        for i in range(3,int(math.sqrt(iNum)+1),2):            if iNum%i==0:                return False        return True    return False#生成器函数def GetByCoroutine(iNum):    while True:        if isPrime(iNum):            iNum=yield iNum        iNum+=1#获取最小的素数def GetMinValue(iIter,iBase):    f=GetByCoroutine(iBase)    #启动第一次    f.send(None) #或者f.next()    #开始迭代    for i in xrange(1,iIter):        #传递等比数列的下一个数字        iTemp=f.send(iBase**i)        print iTemp,if __name__=='__main__':    #基数    base=10    #等比数列迭代次数    iter=5    GetMinValue(iter,base)

上面的过程就是利用协程来完成的。有几点做下说明:

(1)iNum=yield iNum

yield关键字返回了当前iNum的值,而iNum=yield iNumde 意思是生成器函数将“冻结”,直到收到“激活”值,即收到从调用者传来的值(通过send、next等),并赋值给iNum。

(2)iTemp=f.send(iBase**i)

send首先取回被调用的生成器函数yield返回的值(用iTemp接收),同时给yield赋值(iBase**i),并激活生成器函数的下次迭代。

(3)f.send(None)

当用send来“启动”生成器函数的时候,必须首先发送None(或者调用next函数)。因为如果不发送,生成器函数无法去接收我们下面发送的值(iTemp=f.send(iBase**i))。

从上面的例子可以看出,协程比较类似于多线程,但是协程的特点是其在1个线程中执行,不需要来回切换线程,极大减小了消耗。同时,由于是在同一线程中执行,自然不需要使用同步锁(参考博客:Python学习总结笔记(3)–多线程与线程同步)。协程在同一个线程中执行,在多CPU环境下,可以利用多进程+协程,既充分利用多核,又充分发挥协程优点,可以获得极大效率。

0x05后记

生成器和协程是非常强大的工具。可以简化我们的代码逻辑,更可以降低内存消耗,提高代码质量与效率。
Python对协程的支持只是提供了基本的功能,并不完全。诸如gevent库等提供了完善的协程功能,在后续博客中会发布相关内容。

1 0