可迭代对象 >> 迭代器 >> 特殊的迭代器(生成器) >> yield、可以达到完全多任务的效果 >> greenlet模块 >> yield 切换任务 达到多任务的效果 目的 >> gevent 进行再一步的封装 >> 切换任务 达到多任务的效果
for循环的迭代取值,所谓迭代取值,就是在原有情况的基础上增加东西
list = [1,2,3]
# 迭代取值 在原有的情况下增加东西
for i in list:
print(i)
iterable (可迭代对象),只有可迭代对象才能够被for循环迭代
可迭代对象有:列表、元组、集合、字典、字符串这都是python自带的可迭代对象,都可以被 for循环 进行一个操作
# 元组
tuple = (1,2,3)
for i in tuple:
print(i)
# 字符串
str = 'hello'
for i in str:
print(i)
# 集合
set = {1,2,3,'123'}
for i in set:
print(i)
整型就不行:
关于测试是否可迭代:
from collections import Iterable # 测试是否是可迭代对象
class diy:
def __init__(self):
self.names = []
def __iter__(self):
pass
def add_name(self,name):
self.names.append(name)
man1 = diy()
man1.add_name('ll')
man1.add_name('oo')
man1.add_name('pp')
print(isinstance(man1,Iterable))
想要被 for循环成功的迭代:
- 1、判断是否是一个可迭代对象,__iter__方法
- 2、自动调用 iter()函数,得到__iter__方法的返回值
- 3、这个返回值,需要是一个迭代器对象
- 4、自动调用next()去得到__next__里面的返回值
for循环去迭代取值的最终原理:自动调用 next() 函数,来触发 __next__方法,取到里面的值
class diy:
def __init__(self):
self.names = []
# 定义一个实例方法,,,
def add_name(self,name):
# 调用实例方法,添加姓名进列表
self.names.append(name)
# 只要拥有了__iter__方法,那么就能够成为一个可迭代对象
def __iter__(self):
return diyIter(self) # A类实例,init方法被调用,需要一个返回值
class diyIter: # 迭代器 B类里面的 __next__方法 去拿到 A类里面的数据
# 利用传参来得到数据
def __init__(self,obj): # A类的实例,取到A类里面的数据
self.obj = obj # 保存着A类的实例对象,里面的数据都在
# 定义一个初始的索引值
self.start_num = 0
def __iter__(self):
pass
def __next__(self): # 是否真的是 __next__ 方法里面的代码控制着数据的迭代
# 索引自增,才能够往后取值。。。
if self.start_num < len(self.obj.names): # 判断索引和列表长度的值大小
a = self.obj.names[self.start_num] # 取到索引为0的数据
self.start_num += 1 # 索引的自增
return a # return过后的代码
else: # 当超出了取值范围,那么就停止迭代
raise StopIteration # 停止迭代
man = diy()
man.add_name('hello')
man.add_name('olleh')
man.add_name('---')
# 自定义了一个对象,然后可以使用 for 循环 对其迭代取值
for i in man:
print(i)
可迭代对象:
__iter__
迭代器:
__iter__ + __next__
模拟 for循环迭代工作:
a = next(man1)
print(a)
b = next(man1)
print(b)
c = next(man1)
print(c)
迭代器的应用场景:
迭代器里面保存着的就是生成数据的代码,可以达到节约内存的作用
生成器
生成器就一个特殊的迭代器
生成器一个有两种形式:
第一种表现形式:
list = [i for i in range(1,11)]
print(list)
# 生成器里面保存的是生成数据的方法(代码),节约大量的内存的效果
list1 = (i for i in range(1,11))
print(list1) # 生成器对象
for i in list1:
print(i,end=',')
第二种表现形式(需要一个函数里面拥有yield):
# 生成器模板
def fei_bo(c): # 传参控制取到第几位斐波那契数字
a,b =1,1 #定义出斐波那契数列前面两个数字
# 循环变量
i = 0
while i < c:
yield a # 不结束整个语法格式
a,b=b,a+b
i += 1
# 不再是函数的调用
data = fei_bo(5) # 而是生成器对象的创建 >> 好比 类创建对象
print(data) # 可以打印出来data是一个生成器对象,因此它就是一个特殊的迭代器,那就可以使用for循环来迭代取值
for i in data:
print(i,end=',')
def 有三种:
- def 顶格存在的是 >> 函数
- def 类 >> 方法
- def yield >> 生成器模板
yield 的原理分析:
# 生成器模板
def fei_bo(c): # 传参控制取到第几位斐波那契数字
a,b =1,1 #定义出斐波那契数列前面两个数字
# 循环变量
i = 0
while i < c:
yield a # 跟return一样把数据返回出去,但和return不一样的是,yield不会结束这个语法部分,而是暂时挂起
a,b = b, a+b
i += 1
# 不再是函数的调用
data = fei_bo(5) # 而是生成器对象的创建
print(data) # 特殊的迭代器
demo1 = next(data) # 1、第一次调用next()取值,启动生成器模板里面的代码,让代码执行
print(demo1) # 2、demo1得到1,此时他已经暂停挂起
demo2 = next(data) # 3、第二次调用next()取值,代码在哪里暂停就在哪里开始
print(demo2) # 4、demo2得到1,此时他已经暂停挂起了
demo3 = next(data) # 5、第三次调用next()取值
print(demo3) # 6、demo3得到yield返回出来的2,进行一次暂停挂起
# 由于接下来并没有再次使用next()去启动代码,因此112
yield 的特点:
- 1、可以将数据返回出来
- 2、返回出来之后,会暂停挂起
- 3、下一次使用 next() 启动,从哪里暂停,代码从哪里开始
def fei_bo(c): # 传参控制取到第几位斐波那契数字
a,b =1,1 #定义出斐波那契数列前面两个数字
# 循环变量
i = 0
print('---111---')
while i < c:
print('---222---')
yield a
print('---333---')
a,b = b, a+b
i += 1
data = fei_bo(5)
print(data)
demo1 = next(data)
print(demo1)
demo2 = next(data)
print(demo2)
demo3 = next(data)
print(demo3)
可以观察到,在yield那里有个暂停返回值
yield完成多任务:
这个个情况并没有利用多进程、也没有利用多线程,只是利用了yield的暂停挂起机制来完成多任务,在反复不停地打印结果。这样占用的资源最少
回顾一下:进程就是代码的运行、资源的占用;线程就是运行代码的东西
import time
def sing(): # 从普通的函数 >> 生成器模板
while True:
print('---在唱歌---')
time.sleep(0.3)
yield
def dance(): # 从普通的函数 >> 生成器模板
while True:
print('***在跳舞***')
time.sleep(0.3)
yield
def main():
t1 = sing() # 函数的调用 >>> 生成器对象的创建
t2 = dance()
# 既然是生成器,那么就是特殊的迭代器 for >>> next()函数去取值
while True:
try:
next(t1) # 启动生成器模板 执行里面的代码
next(t2) # 如果想要不停的启动,放到循环里面
except Exception:
break
if __name__ == '__main__':
main()
用协程完成多任务,是占用资源最小的方式
greenlet完成多任务:
from greenlet import greenlet
import time
def sing(): # 任务A
while True:
print('---在唱歌---')
time.sleep(0.3)
g2.switch() # 切换到对象创建的时候指定的代码部分
def dance(): # 任务B
while True:
print('***在跳舞***')
time.sleep(0.3)
g1.switch() # 切换
g1 = greenlet(sing) # 创建对象 需要定制执行的代码部分
g2 = greenlet(dance)
def main():
g1.switch() # 切换到对象创建的时候指定的代码部分
if __name__ == '__main__':
main()
利用在各个任务里面进行来回切换,,达到多任务的效果(greenlet 里面的 switch方法)
from greenlet import greenlet
import time
def sing(): # 任务A
while True:
print('---在唱歌---')
time.sleep(0.3)
g2.switch() # 切换到对象创建的时候指定的代码部分,即 “在跳舞”
def dance(): # 任务B
while True:
print('***在跳舞***')
time.sleep(0.3)
g1.switch() # 切换到g1指定的代码部分,即 ‘在唱歌’
g1 = greenlet(sing) # 创建对象 需要定制执行的代码部分
g2 = greenlet(dance)
def main():
g2.switch() # 切换到对象创建的时候指定的代码部分
if __name__ == '__main__':
main()
greenlet 完成多任务的机制,其实是对yield进行了简单的封装的
gevent完成多任务(gevent实质上是对 greenlet 进行进一步的封装):
协程
实质上 gevent 是对 greenlet 进行了一个封装
import gevent
import time
def sing():
for i in range(1,6):
print('在唱第%s首歌。。。' % i)
def dance():
for i in range(1,6):
print('在跳第%s支舞---' % i)
g1 = gevent.spawn(sing) # 任务作为参数传进来
g2 = gevent.spawn(dance) # 跟子进程、子线程的创建十分相像
def main():
g1.join() # 用来启动函数;区别:多进程多线程启动对象是start()方法,而协程是gevent
g2.join()
if __name__ == '__main__':
main()
优点是:
并不像 greenlet 一样需要手动调用 switch() 去进行切换 ,它可以达到一个自动切换的目的
自动切换需要满足的条件:
gevent 在碰到延时操作的时候就会自动切换任务,达到多任务的效果
延时操作 导致 自动切换任务,达到一个多任务的效果
import gevent
import time
def sing():
for i in range(1,6):
print('在唱第%s首歌。。。' % i)
gevent.sleep(0.5) # 注意这里!gevent有自己的延时操作,不是用time
def dance():
for i in range(1,6):
print('在跳第%s支舞---' % i)
gevent.sleep(0.5)
g1 = gevent.spawn(sing) # 任务作为参数传进来
g2 = gevent.spawn(dance) # 跟子进程、子线程的创建十分相像
def main():
g1.join() # 用来启动函数;区别:多进程多线程启动对象是start()方法,而协程是gevent
g2.join()
if __name__ == '__main__':
main()
gevent 打补丁延时
依赖关系:
协程依赖代码的执行和方法的调用,所以协程依赖于线程,而线程又依赖于进程,没有进程就没有线程啊!而协程又依赖于线程,这就是它们三个之间的一个依赖关系
在耗费资源方面:
进程耗费资源最大,线程次之,协程是最少的(协程只是依赖方法的调用来达到切换的目的)
多任务的完成:
- 多进程:子进程,代码 + 资源
- 多线程:子线程、资源的产生
- 协程:利用的是自己的延时操作(gevent),会有一点不好,就是十分不方便
利用打补丁来触发原生的延时操作
monkey.patch_all()
将程序中的原生延时操作,通过自动转化为 gevent 自己的延时操作
import gevent
import time
from gevent import monkey
# 打补丁
monkey.patch_all() # 将程序当中的原生延时操作,通过自动化转化为gevent自己的延时操作
# 目的 一边唱歌 一边跳舞 达到多任务的目的
def sing():
for i in range(1,6):
print('在唱第%s首歌。。。' % i)
time.sleep(0.5)
def dance():
for i in range(1,6):
print('在跳第%s支舞---' % i)
time.sleep(0.5) # 因为前面打补丁的操作,将这里的time.sleep >>转化成>> gevent.sleep
g1 = gevent.spawn(sing) # 任务作为参数传进来
g2 = gevent.spawn(dance) # 跟子进程、子线程的创建十分相像
def main():
g1.join() # 用来启动函数;区别:多进程多线程启动对象是start()方法,而协程是gevent
g2.join()
if __name__ == '__main__':
main()
另外还有一个知识点:
gevent.joinall([ ])
这个起到优化代码的作用
import gevent
import time
from gevent import monkey
# 打补丁
monkey.patch_all() # 将程序当中的原生延时操作,通过自动化转化为gevent自己的延时操作
# 目的 一边唱歌 一边跳舞 达到多任务的目的
def sing():
for i in range(1,6):
print('在唱第%s首歌。。。' % i)
time.sleep(0.5)
def dance():
for i in range(1,6):
print('在跳第%s支舞---' % i)
time.sleep(0.5)
def main():
gevent.joinall(
[gevent.spawn(sing),
gevent.spawn(dance),
]
)
if __name__ == '__main__':
main()
协程完成多任务是利用延时操作切换任务,达到多任务的效果
多任务小结:
多进程(互不干扰,最稳定最安全,但耗费大量资源):
进程:程序运行起来,代码的运行 + 资源的调用(运行内存的占用,CPU,网络,等等),进程就是资源分配的基本单位
Process
多线程(耗费资源相对较少):
一个进程里面至少有一个线程去执行代码(主线程),,,线程是操作系统调度的基本单位
Thread
协程(耗费资源是最少的,必须依赖延时操作,如果没有延时操作那就是多线程了):
gevent >> greenlet 切换 >> yield
利用延时操作,自动切换任务,达到一个多任务的效果