多任务之协程
迭代器
在python中常见的迭代方式实例
类创建的对象是否可以迭代呢,可以看下面的代码
代码示例
class Classmate(object):
def __init__(self):
self.names = list()
def add(self, name):
self.names.append(name)
classmate = Classmate()
classmate.add("张三")
classmate.add("李四")
classmate.add("王五")
for name in classmate:
print(name)
运行后可以看到结果报错,不能迭代
在python中通过类创建的对象必须拥有__iter__方法,并且该方法返回的对象,必须要拥有__iter__方法和__next__方法才可以进行迭代。
其迭代流程如下:
1.在进行迭代之前,会进行判断该对象是否可以迭代,简单理解就是是否拥有__iter__方法。
2.在第一步成立的情况下,会调用iter函数得到该对象的__iter__的返回值,该返回值为一个迭代器。
3.第二步的返回的对象拥有__iter__和__next__方法,迭代的结果为__next__方法的返回值
将上面的代码改造一下
class Classmate(object):
def __init__(self):
self.names = list()
def add(self, name):
self.names.append(name)
def __iter__(self):
return ClassIterator()
class ClassIterator(object):
def __iter__(self):
pass
def __next__(self):
return 123
classmate = Classmate()
classmate.add("张三")
classmate.add("李四")
classmate.add("王五")
for name in classmate:
print(name)
代码执行结果可以看到,能迭代但是所返回的值和我们要的值不一样,那是因为返回的是__next__方法的返回值
现在把代码再完善一下:
class Classmate(object):
def __init__(self):
self.names = list()
def add(self, name):
self.names.append(name)
def __iter__(self):
return ClassIterator(self)
class ClassIterator(object):
def __init__(self, obj):
self.obj =obj
self.num = 0
def __iter__(self):
pass
def __next__(self):
if self.num < len(self.obj.names):
ret = self.obj.names[self.num]
self.num += 1
return ret
else:
raise StopIteration
classmate = Classmate()
classmate.add("张三")
classmate.add("李四")
classmate.add("王五")
for name in classmate:
print(name)
代码执行:
可以代码执行达到了想要的效果,上面代码改造增加了一个判断值,当列表取值取完了,代码并不会停下,会返回None,所有加了个返回异常,当迭代收到这个异常时就会停止迭代
上面的代码还可以完善一下,这样写两个类感觉有点多,可以合成以一个。
class Classmate(object):
def __init__(self):
self.names = list()
self.num = 0
def add(self, name):
self.names.append(name)
def __iter__(self):
return self
def __next__(self):
if self.num < len(self.names):
ret = self.names[self.num]
self.num += 1
return ret
else:
raise StopIteration
classmate = Classmate()
classmate.add("张三")
classmate.add("李四")
classmate.add("王五")
for name in classmate:
print(name)
执行结果:
说了这么多,说下迭代器的优势,开头迭代字符串“python”这个是常见的方式,当字符串过小时,占用不了多少内存空间,假如需要迭代的字符串大到内存都无法加载的时候,这个时候使用fox循环进行迭代就会出问题,而说迭代器它是生成数据的一个方式,只会占用很少的内存空间。
生成器
以斐波那契数列数列来示例介绍
可以写个简单的代码
def fibonacci(num):
a, b = 0, 1
current_num = 0
while current_num < num:
print(a)
a, b = b, a+b
current_num += 1
fibonacci(10)
执行效果
将代码改造成生成器的样子
def fibonacci(num):
a, b = 0, 1
current_num = 0
while current_num < num:
#print(a)
yield a
a, b = b, a+b
current_num += 1
obj = fibonacci(10)
for num in obj:
print(num)
如果一个函数中有yield语句, 那么这个就不再是函数,而是一个生成器模板,在调用模板时需要创建对象。
因为生成器是特殊的迭代器,就可以通过next方法来获得参数
def fibonacci(num):
print("----1----")
a, b = 0, 1
current_num = 0
while current_num < num:
print("----2----")
#print(a)
yield a
print("----3----")
a, b = b, a+b
current_num += 1
print("----4----")
obj = fibonacci(10)
ret = next(obj)
print(ret)
ret = next(obj)
print(ret)
通过增加注释的方式来执行代码可以看到生成器工作的过程,当返回第一个值的时候,代码执行到yield a 时返回a的值,当要返回第二个值时,代码执行会从yield a 往下执行到yield a 返回a的值
执行多任务
通过生成器的执行方式,就可以通过yield 方式执行多任务
import time
def test1():
while True:
print("----1----")
time.sleep(1)
yield
def test2():
while True:
print("----2---")
time.sleep(1)
yield
def main():
y1 = test1()
y2 = test2()
while True:
next(y1)
next(y2)
if __name__ == '__main__':
main()
结果可以看到两个函数同时执行完成多任务
但是一般协程都不使用yield而是使用gevent方式,使用gevent,当遇到延时就会执行下一个函数,所以要设置延时或者本身要有延时(阻塞)
import time
import gevent
def test1():
while True:
print("----1----")
#time.sleep(1)
gevent.sleep(1)
def test2():
while True:
print("----2---")
#time.sleep(1)
gevent.sleep(1)
def main():
g1 = gevent.spawn(test1)
g2 = gevent.spawn(test2)
g1.join()
g2.join()
if __name__ == '__main__':
main()
执行效果如下,有几点要注意的
1.代码和之前线程,进程一样,传递参数的时候后面不是元组
2.gevent所有延时的方法都必须使用gevent.xxx比如time.sleep要写成gevent.sleep
3.当多任务过多时结尾可以写成
gevent.joinall([
gevent.spawn(text1),
gevent.spawn(text2)
])
线程,进程,协程对比
1.进程是资源分配的单位
2.线程是操作系统调度的单位
3.进程切换需要的资源最大,效率很低
4.线程切换需要的资源一般,效率一般(不考虑gil情况下)
5.协程切换任务资源很少,效率高
6.多进程、多线程根据cpu核数不一样可能是并行的,但协程是在一个线程中所有是并发