多任务之协程
迭代器
- 迭代:是访问集合元素的一种方式,用
for....in...
从list、tuple、str
等类型中依次取数据,这种遍历也称之为迭代 - 迭代对象:可以通过
for....in...
这类语句迭代读取数据的对象 - 迭代器:是一个可以记住遍历的位置的对象。
实现一个可迭代对象
from collections import Iterable
import time
class Classmate(object):
def __init__(self):
self.names = list()
self.i = 0
def add(self, name):
self.names.append(name)
def __iter__(self):
"""把对象变成可以迭代 即可以用for """
return self
def __next__(self):
# 方法内部定义变量,下次调用不会保留上次的值,需定义在__init__内
# i = 0
if self.i < len(self.names):
ret = self.names[self.i]
self.i += 1
return ret
else:
raise StopIteration # 自定义一个异常,告诉for已经遍历完毕了
classmate = Classmate()
classmate.add("老王")
classmate.add("张三")
classmate.add("李四")
print("判断classmate是否可以迭代的对象:", isinstance(classmate, Iterable))
iter(classmate)
for name in classmate:
print(name)
time.sleep(1)
- 迭代器最核心的功能是储存生成数据的方式代码,而不是生成数据的本身,即不需要一个空间来存储数据,可节省大量资源空间
斐波拉契数列(Fibonacci)
数列中第一个数为0,第二个数为1,其后的每一个数都可由前两个数相加得到:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …
nums = list()
a, b, i = 0, 1, 0
while i < 10:
nums.append(a)
a, b = b, a + b
i += 1
for num in nums:
print(num)
用迭代器实现Fibonacci
class Fibonacci(object):
def __init__(self, all_num):
self.all_num = all_num
self.i = 0 # 用来保存当前生成到数列中的第几个数了
self.a = 0 # 用来保存前前一个数,初始值为数列中的第一个数0
self.b = 1 # 用来保存前一个数,初始值为数列中的第二个数1
def __iter__(self):
"""迭代器的__iter__返回自身即可"""
return self
def __next__(self):
"""被next()函数调用来获取下一个数"""
if self.i < self.all_num:
ret = self.a
self.a, self.b = self.b, self.a + self.b
self.i += 1
return ret
else:
raise StopIteration
fibo = Fibonacci(10)
for num in fibo:
print(num)
生成器
- 生成器是一类特殊的迭代器
生成器实现Fibonacci
def create_num(all_num):
a, b = 0, 1
i = 0
while i < all_num:
# print(a)
# 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器
yield a
a, b = b, a + b
i += 1
# 这时调用,不是在调用函数,而是创建一个生成器对象
# create_num(10)
obj = create_num(10)
for x in obj:
print(x)
- 简单来说:只要在def中有
yield
关键字的 就称为 生成器,不在是函数- yield关键字有两点作用:
- 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
- 将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用
- 可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
用异常判断生成器结束
def create_num(all_num):
a, b = 0, 1
i = 0
while i < all_num:
# print(a)
# 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器
yield a
a, b = b, a + b
i += 1
return "完成..."
obj = create_num(10) # 这时调用,不是在调用函数,而是创建一个生成器对象
while True:
try:
ret = next(obj)
print(ret)
except Exception as ret:
print(ret.value)
break
用send启动生成器
- 可以使用
next()
函数来唤醒生成器继续执行外,还可以使用send()
函数来唤醒执行。使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据
def create_num(all_num):
a, b = 0, 1
i = 0
while i < all_num:
ret = yield a
print(">>>>ret>>>>", ret)
a, b = b, a + b
i += 1
obj = create_num(10)
ret = next(obj)
print(ret)
# send里面的数据会传递给第5行,当做yield a的结果,然后ret保存这个结果
# send的结果会是下一次调用yield时 yield后面的值
ret = obj.send("hhhh") # send一般不会的放到第一次启动,除非传递的是None
print(ret)
协程
- 协程是python个中另外一种实现多任务的方式
- 通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定
yield
import time
def task_1():
while True:
print("----1----")
time.sleep(0.1)
yield # 只需加这个就可以
def task_2():
while True:
print("----2----")
time.sleep(0.1)
yield
def main():
t1 = task_1()
t2 = task_2()
# 先让t1运行一会,当t1中遇到yield的时候,再返回到 while True
# 执行t2,当它遇到yield的时候,再次切换到t1中
# 相当于t1/t2交替执行,这就是协程完成多任务
while True:
next(t1)
next(t2)
if __name__ == '__main__':
main()
greenlet
- 为了更简单的切换任务,python中的
greenlet
模块对其封装 greenlet
可能需要单独安装(有些默认没有装)
from greenlet import greenlet
import time
def test_1():
while True:
print("----1----")
gr2.switch()
time.sleep(0.5) # 延时为了更好的看效果
def test_2():
while True:
print("----2----")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test_1)
gr2 = greenlet(test_2)
gr1.switch() # 切换到gr1中运行
gevent(最常用!!这个是真重点!!!)
- 虽然
greenlet
已经实现了协程,但还还需去单独去切换 gevent
模块就比之前的两个都好用,而且可以自动切换
import gevent
import time
from gevent import monkey
monkey.patch_all() # 打补丁,将下面的延时操作变成 gevent.sleep(0.5)
def f1(n):
for i in range(n):
print(gevent.getcurrent(), i)
#用来模拟一个耗时操作,注意不是time模块中的sleep
time.sleep(0.5) # 若没有上面的补丁,则这句话没效果,就是安顺序执行,不是多任务
#遇到延时等待,切换到下一个执行
def f2(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
def f3(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
gevent.joinall([
gevent.spawn(f1, 5),
gevent.spawn(f2, 5),
gevent.spawn(f3, 5)
])
# g1.join()
# g2.join()
# g3.join()
gevent
任务切换是需要堵塞,如,当执行到 f1 里的time.sleep(0.5)
时,gevent
就发现遇到了延时,程序处于等待状态,这时,gevent
就会切换到 f2 中执行,同理,就实现了多任务(并发)
案例-图片的爬取(gevent)
import urllib.request
import gevent
from gevent import monkey
monkey.patch_all() # 有耗时操作时就需要,反正加上也没毛病
def download(img_name, img_url):
req = urllib.request.urlopen(img_url)
img_content = req.read()
with open(img_name, "wb") as f:
f.write(img_content)
def main():
gevent.joinall([
gevent.spawn(download, "1.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2019/05/05/6463290_20190505155227_small.jpg"),
gevent.spawn(download, "2.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2019/04/03/6532591_20190403195850_small.jpg"),
gevent.spawn(download, "3.jpg", "https://rpic.douyucdn.cn/live-cover/appCovers/2019/04/24/6496091_20190424210517_small.jpg"),
])
if __name__ == '__main__':
main()
- 试验试验而已,爬了三张妹子。。。。。把地址换了也可以是下载视频,音乐啥的。。
END…