在了解生成器之前,我们需要补充一下迭代器的知识:Python 迭代器(iterator)(若已了解,请自动忽略)
一、生成器
1.什么是生成器generator:
在迭代器中,我们需要来定义一个变量来记录当前所在的状态,进而才能根据当前状态来生成下一个数据。而在生成器中,我们不需再定义一个变量来记住当前状态,会自动记住。可以说生成器是一类特殊的迭代器。
2.创建生成器
(1)小括号法
我们直接将列表推导式中的中括号改为小括号即可。因为生成器是一种特殊的迭代器,是所以可以用迭代的方式来验证。如下:
In [1]: a = [x*2 for x in range(10)]
In [2]: a
Out[2]: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
In [3]: a = a = (x*2 for x in range(10))
In [4]: a
Out[4]: <generator object <genexpr> at 0x7fc2d3646888>
In [5]: next(a)
Out[5]: 0
In [6]: next(a)
Out[6]: 2
In [7]: next(a)
Out[7]: 4
可以看出,由列表推导式生成 的列表是一次性生成所有数据,而后者是返回一个生成器,在调用时自动生成相应数据。
(2)yield 法
如果一个函数中含有yield语句,那么这个函数就称为生成器。而在引用这个函数的时候不再是调用函数,而是创建一个生成器对象。在生成器运行的过程中,每次遇到yield都会暂停并保存当前所有的运行信息,返回yield后的值。在下一次运行时从当前位置开始运行。
3.yield的工作原理
我们通过一个例子来验证一下
import time
def test1():
print("mama~")
time.sleep(0.2)
yield
def test2():
print("我想吃烤山药~")
time.sleep(1)
yield
def main():
while True:
next(test1())
print("============")
next(test2())
if __name__ == "__main__":
main()
运行结果如下:
根据结果可以看出,生成器每次都是在yield的地方返回数值,停止到这个地方,直到下一次的调用,再从此处开始。
二、协程
1.协程的概念
协程,英文名是coroutine。其原理是当一个函数A正在执行时,可以随时中断,去执行函数B,然后再回来继续执行函数A。
2.greenlet
为了更好的使用协程来完成多任务,python中的greenlet对yield进行了封装,从而使得切换任务变得更加简单。在使用greenlet前,我们需要下载greenlet模块,sudo pip3 install greenlet
import time
from greenlet import greenlet
def task1():
while True:
print("=====mama====")
g2.switch()
time.sleep(0.5)
def task2():
while True:
print("====我想吃烤山yao====")
g1.switch()
time.sleep(1)
g1 = greenlet(task1)
print("=================")
g2 = greenlet(task2)
g1.switch()
运行结果如下:
3.greenlet的工作原理
4.gevent
greenlet已经实现了协程,但是还是需要人工切换。而gevent实现了自动切换任务的功能。可以说是比greenlet更强的一个包装。其原理是当一个greenlet遇到i/o操作时,就会自动切换到其它的greenlet,等i/o操作结束后,再在适当的时候切换回来,继续执行未完成的程序。 由于io操作非常耗时,经常使程序处于等待状态,因此使用gevent就可以为我们自动切换协程,就能保证走总有greenlet在执行,而不是等待i/o。
import gevent
from gevent import monkey
import time
# 给所有的延时从操作打补丁:将其换为gevent内对应的延时操作
monkey.patch_all()
def f1(n):
for i in range(n):
print("___",i,"___")
time.sleep(0.5)
def f2(n):
for i in range(n):
print("___",i,"___")
time.sleep(0.5)
def f3(n):
for i in range(n):
print("___",i,"___")
time.sleep(0.5)
g1 = gevent.spawn(f1,5)
g2 = gevent.spawn(f1,5)
g3 = gevent.spawn(f1,5)
# 运行所有的协程
gevent.joinall([g1,g2,g3])
#g1.join()
#g2.join()
#g3.join()
运行效果如下:
5.案例
协程版图片下载器
import urllib.request
import gevent
from gevent import monkey
nums = 1
monkey.patch_all() # 将所有的等待程序改为gevent的等待程序
# 定义一个下载器
def downloader(url):
global nums
# 打开网址
req = urllib.request.urlopen(url)
# 读取内容
data = req.read()
# 写入本地文件
with open("images/"+str(nums)+".jpg","wb") as f:
f.write(data)
nums += 1
def main():
g1 = gevent.spawn(downloader,"https://sta-op.douyucdn.cn/dycatr/7e189c7fedb3aa96e84d9fefcc892ec1.png?x-oss-process=image/format,webp/quality,q_70")
g2 = gevent.spawn(downloader,"https://sta-op.douyucdn.cn/dycatr/08dcd21b98a6b71bb1d37b0c30376734.png?x-oss-process=image/format,webp/quality,q_70")
g3 = gevent.spawn(downloader,"https://sta-op.douyucdn.cn/dycatr/e3156f581df48b385f67fd1cc775f022.png?x-oss-process=image/format,webp/quality,q_70")
g4 = gevent.spawn(downloader,"https://sta-op.douyucdn.cn/dycatr/a783ec694e455942f925801fb449243d.png?x-oss-process=image/format,webp/quality,q_70")
g5 = gevent.spawn(downloader,"https://dss0.baidu.com/6LZXsjikBxIFlNKl8IuM_a/tb/cms/ngmis/file_1584672553807.jpg")
g6 = gevent.spawn(downloader,"https://sta-op.douyucdn.cn/dycatr/3de8cf9f376ef5ee1bbb930868f7f402.png?x-oss-process=image/format,webp/quality,q_70")
gevent.joinall([g1,g2,g3,g4,g5,g6])
if __name__ == "__main__":
main()