可迭代对象、迭代器、生成器

可迭代对象 >> 迭代器 >> 特殊的迭代器(生成器) >> 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
利用延时操作,自动切换任务,达到一个多任务的效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值