协程
迭代器
迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历的位置的对象,接待器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不能后退。
判断一个对象是否可以迭代:
from collections import Iterable # 调用一个迭代类
isinstance([11, 22, 33], Iterable) # 判断是否是迭代的子类
>>True
问题:如果自己创建一个带列表的类,是否能迭代呢?
例:
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)
'''会提示错误,不可迭代'''
结论是不可以的,需要定义__iter__类
如下例:
from collections import Iterable
class Classmate(object):
def __init__(self):
self.names = list()
def add(self, name):
self.names.append(name)
def __iter__(self):
'''如果想让一个对象成为一个可以迭代的对象,即可以使用for
那么必须实现__iter__方法'''
pass
classmate = Classmate()
classmate.add("老王")
classmate.add("网二")
classmate.add("张三")
print(isinstance(classmate, Iterable))
>>True
>但依旧不可以使用for--->print
>需要让__iter__返回一个对象的引用,对象引用的类内部必须要有__iter__方法和__next__方法
在使用for temp in xxxx_obj的过程中发生了什么呢?
1.判断xxxx_obj是否可以迭代(只要xxxx_obj内有__iter__函数即可)
2.在第1步成立的前提下,调用iter函数得到xxxx_obj对象的__iter__方法的返回值
3.__iter__方法的返回值是一个迭代器
下面我们创建一个自带__iter__和__next__方法的类,并且在原先创建的类的__iter__方法中return新创建的类:
from collections import Iterable
from collections import Iterator
import time
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.current_num = 0
def __iter__(self):
pass
def __next__(self):
if self.current_num < len(self.obj.names):
ret = self.obj.names[self.current_num]
self.current_num += 1
return ret
else:
raise StopIteration # 不raise会一直打印None
classmate = Classmate()
classmate.add("老王")
classmate.add("网二")
classmate.add("张三")
# 判断是否可迭代
print(isinstance(classmate, Iterable))
classmate_iterator = iter(classmate)
# 判断是否是迭代器
print(isinstance(classmate_iterator, Iterator))
for name in classmate:
print(name)
time.sleep(1)
既然可以通过返回带有__iter__和__next__方法的类来使其可迭代,我们当然可以让原来本身的类也有__next__方法,然后再__iter__中返回self,这样在使用for的时候就会先判断我们这个类是否是可迭代的?即是否拥有__iter__方法?√
自动调用iter()方法,即会调用类中的__next__方法,返回对应的值
例:
class Classmate(object):
def __init__(self):
self.names = list()
self.current_num = 0
def add(self, name):
self.names.append(name)
def __iter__(self):
return self
def __next__(self):
if self.current_num < len(self.names):
ret = self.names[self.current_num]
self.current_num += 1
return ret
else:
raise StopIteration # 不raise会一直打印None
classmate = Classmate()
classmate.add("老王")
classmate.add("网二")
classmate.add("张三")
for name in classmate:
print(name)
time.sleep(1)
迭代器的应用场景:用于调用存储的数据
问题:为什么我们不直接把数据存到一个列表里呢?因为直接放列表会占用内存,而迭代器可以随用随取,不会占用
例:斐波那契数列
class Fabonacci(object):
def __init__(self, all_num):
self.all_num = all_num
self.current_num = 0
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
if self.current_num < self.all_num:
ret = self.a
self.a, self.b = self.b, self.a+self.b
self.current_num += 1
return ret
else:
raise StopIteration
fibo = Fabonacci(10)
for num in fibo:
print(num)
>>0
1
1
2
3
5
8
13
21
34
注:类型转换的实质不是单纯的转换,以a = (1, 3, 5, 7)元组为例,list(a)的实质是先生成一个空列表[ ],然后用元组a的迭代器依次取值,放入空列表,等到取完触发异常结束
生成器
生成器是一种特殊的迭代器,主要通过yield来迭代返回生成
例:
def creat_num(all_num):
print("----1----")
a, b = 0, 1
current_num = 0
while current_num < all_num:
print("----2----")
# print(a)
yield a
'''如果一个函数中有yield语句,那么这个就不再是函数,而是一个生成器模版'''
print("----3----")
a, b = b, a+b
current_num += 1
print("----4----")
'''如果在调用create_num的时候,函数中有yield,
那么就不是调用函数,而是创建一个生成器对象
可以创建多个生成器你,例如obj2 = creat_num(3)'''
obj = creat_num(10)
ret = next(obj)
print(ret)
ret = next(obj)
print(ret)
>>----1----
----2----
0
----3----
----4----
----2----
1
注:如果生成器最后有return的值,则可以用生成器的对象ret调用value,即ret.value
生成器的send
obj.send()相当于传递了一个值v到生成器内部,使yield a的结果为传递进去的值v,同时send的结果会变成a的值,如果需要对后面的代码进行调控,就可以利用send传入进的值v
例:
def creat_num(all_num):
a, b = 0, 1
current_num = 0
while current_num < all_num:
ret = yield a
print("----ret----", ret)
a, b = b, a+b
current_num += 1
obj = creat_num(10)
ret = next(obj)
print(ret)
ret = obj.send("hahahha")
print(ret)
>>0
----ret---- hahahha
1
迭代器和生成器小结:总而言之,迭代器可以缩减占用空间,生成器可以按需获取返回值而不停止函数运行
使用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()
while True:
next(t1)
next(t2)
if __name__ == "__main__":
main()
使用greeenlet实现多任务
相当于把yield的用法简化了,原先在True循环中调用next来达到切换进程,现在则是把这个控制步骤分别撞到各个函数当中,并且不再需要yield,而用greenlet自己封装的功能来达到切换效果
例:
from greenlet import greenlet
import time
def test1():
while True:
print("----A----")
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print("----B----")
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
使用gevent实现多进程
gevent是一个网络的并发库,相当于对greenlet再次进行了封装
携程依赖于线程,线程依赖于进程
例:
import gevent
import time
def f1(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5)
def f2(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5)
def f3(n):
for i in range(n):
print(gevent.getcurrent(), i)
gevent.sleep(0.5)
g1 = gevent.spawn(f1, 5) # 相当于创建了greenlet对象
g2 = gevent.spawn(f2, 5)
g3 = gevent.spawn(f3, 5)
g1.join()
g2.join()
g3.join()
如果想用gevent来实现多任务,则需要将程序内的延时操作代码都替换成gevent的方法,例如上例本来应该用的time.sleep,替换成了gevent.sleep。但这样的话会非常麻烦,这时候就需要打一个补丁:monkey.patch_all()
例:
import gevent
import time
from gevent import monkey
monkey.patch_all()
def f1(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
# gevent.sleep(0.5)
def f2(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
# gevent.sleep(0.5)
def f3(n):
for i in range(n):
print(gevent.getcurrent(), i)
time.sleep(0.5)
# gevent.sleep(0.5)
g1 = gevent.spawn(f1, 5) # 相当于创建了greenlet对象
g2 = gevent.spawn(f2, 5)
g3 = gevent.spawn(f3, 5)
gevent.joinall([g1, g2, g3])
因为这种方式实际上是几个线程交替进行,所以不适用于聊天器之类的自由度比较高的设计
进程、线程、协程对比
1.进程是资源分配的单位
2.线程是操作系统调度的单位
3.进程切换需要的资源很大,效率很低
4.线程切换需要的资源一般,效率一般
5.协程切换任务资源很小,效率高
6.多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中,所以是并发