| by 鲍 建伟 | No comments

Python的GIL和多进程研究(3)

前几天研究协程把我研究的头皮发麻,今天下午冷静了一下,重新看看。

在Python中说到协程,那就要先说生成器。

什么是生成器呢?

先用斐波那契数列举个例子,如果不使用生成器,我们可以这样写:

def fib(max):
    a = 1
    b = 1
    result = []
    for i in range(max):
        a, b = (b, a + b)
        result.append((a, b))
        print(result)
    return 'over'

if __name__ == '__main__':
    fib(10)

如果用生成器是这样:

def fib(max):
    a = 1
    b = 1
    for i in range(max):
        yield a, b
        a, b = (b, a + b)
    return 'over'

if __name__ == '__main__':
    for i in fib(10):
        print(i)

好像没区别啊?

别急,我们继续看,如果把传入参数设置为100000000000(随便你按多少个0),如果使用第一种方法,打开资源监视器,可以看到CPU占用率肉眼可见的飙升,也会吃掉很多内存,当然你可以说,像第一份代码一样,我有病啊?干嘛存进List里面?我不存不就行了?

这就是我得例子举得有问题,哈哈哈,因为生成器的最佳应用场景是:你不想同一时间将所有计算出来的大量结果集分配到内存当中。再举一个极端点的例子:

sum([i for i in range(10000000000)])
sum(i for i in range(10000000000))

第一行代码一分钟内就把我的内存爆了(16G),第二行代码就很佛系,几乎没啥内存变化。这就是生成器最优秀的特性:延迟计算,也就是说,它不会一次生成所有的结果,这对于超大数据量的处理非常有用。

接下来解释上文中第二段代码:

def fib(max):
    a = 1
    b = 1
    for i in range(max):
        yield a, b
        a, b = (b, a + b)
    return 'over'

if __name__ == '__main__':
    for i in fib(10):
        print(i)

为什么说fib这个方法是生成器?就是因为第五行中的yield,只要一个函数中包含yield关键字,那他就是不是个普通的函数,而是一个生成器。我们知道,函数都是顺序执行的,当遇到return语句或者执行到最后一行语句就返回。而变成生成器的函数,每次使用next()调用时执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

说起来有点绕,看下面的代码就好:

def generator_test():
    yield print('第一步')
    yield print('第二步')
    yield print('第三步')
    yield print('第四步')

if __name__ == '__main__':
    x = generator_test()
    next(x)
    next(x)
    next(x)
    next(x)
输出为:
第一步
第二步
第三步
第四步

待续//

发表评论