05 迭代器 生成器
一、 迭代器定义 _iter_
1.什么是迭代器
迭代器:迭代取值的工具
迭代:更新换代(重复)的过程,每次的迭代都必须基于上一次的结果
2.为什么要用
迭代器给你提供了一种不依赖于索引取值的方式
3.可迭代对象
只要内置有__iter__方法的都叫做可迭代对象
补充:针对双下线开头双下划线结尾的方法
推荐读:双下+方法名
基本数据类型中除了数字型都是可迭代对象,文件迭代后还是自己,可迭代对象执行内置的__iter__方法得到就是该对象的迭代器对象 # res = d.__iter__() # res = iter(d) # print(s.__len__()) # 简化成了len(s)
4.迭代器总结 1.可迭代对象:内置有__iter__方法的 2.迭代器对象:既内置有__iter__也内置有__next__方法 3.迭代取值: 优点: 1.不依赖于索引取值 2.内存中永远只占一份空间,不会导致内存溢出 缺点: 1.不能够获取指定的元素 2.取完之后会报StopIteration错
二、 迭代器取值 _next_
迭代器对象 1.内置有__iter__方法 2.内置有__next__方法 ps:迭代器一定是可迭代对象,而可迭代对象不一定是迭代器对象 3.迭代器对象无论执行多少次__iter__方法得到的还是迭代器对象本身 4.迭代器取值只能往后依次取 不能后退
2.1 不完美取值
d = {'name':'jason','password':'123','hobby':'泡m'}
iter_d = d.__iter__() #生成一个迭代器,将可迭代对象d转换成迭代器对象
print(iter_d.__next__()) # name迭代器对象的取值 必须用__next__
print(iter_d.__next__()) #password
print(iter_d.__next__()) #hobby
print(iter_d.__next__()) # 取完了 报错StopIteration ,都报错了你能忍?怎么办?
2.2 异常处理 防止迭代取值溢出范围
while True:
try:
print(iter_d.__next__())
except StopIteration: #上面的print报错后如果和条件中的报错信息一样不会报错,而是继续向下执行,不一样的错误还是会报错
# print('这谁顶得住!')
break
异常有两大类
1.语法结构错误:需要你当场修改 异常捕获没法完成
2.逻辑错误:异常捕获可以处理
三、 for 循环原理
for循环内部的本质 :(for循环后面的in关键 跟的是一个可迭代对象)
1.将in后面的对象调用__iter__转换成迭代器对象(不管谁来,先执行_iter_这个操作,因此文件即使是迭代器对象也要拥有_iter_方法才能支持for循环)
2.调用__next__迭代取值
3.内部有异常捕获,捕获StopIteration,当__next__报这个错 自动结束循环
四、生成器 = 自定义迭代器 yield
生成器:用户自定义的迭代器,本质就是迭代器 def func(): print('first') yield 相应参数 # 函数内如果有yield关键字,那么加括号执行函数的时候并不会触发函数体代码的运行 g = func() # 生成器初始化:将函数变成迭代器,函数体代码不会执行
4.1 用yield返回值
# yield后面跟的值就是调用迭代器__next__方法你能得到的值
# yield既可以返回一个值也可以返回多个值 并且多个值也是按照元组的形式返回
def func():
print('first')
yield 666 # yield相当于一个暂停器,并且有返回功能
print('second')
yield 777
print('forth')
yield
g = func() # 生成器初始化:将函数变成迭代器
print(g) #<generator object func at 0x0000000002170F68> 是一个迭代器
print(g.__next__()) #666 调用一次_next_出来一个返回值,然后程序会停在yield,等待下一个启动器触发
print(g.__next__()) #777
print(g.__next__()) #None print('forth')后面如果不写 yield,就会报错,写了后面没有值相当于空返回None结束程序
4.2自定义range练习
1.内置的range for i in range(1,10,2): print(i) #13579 2.自定义一个range def my_range(start,end,step=1): while start < end: yield start start += step for j in my_range(1,100,2): print(j) #13579
4.3 yield可以用来传参
# yield支持外界为其传参 def dog(name): print('%s 准备开吃'%name) while True: food = yield print('%s 吃了 %s'%(name,food)) g = dog('egon') #无结果 当函数内有yield关键字的时候,调用该函数不会执行函数体代码,而是将函数变成生成器 g.__next__() #egon 准备开吃 这一次运行一定不能少,必须有这行代码生成器才真正执行,遇到yield暂停,必须先将代码运行至yield ,才能够为其传值 g.__next__() #egon 吃了 None 遇到一次_next_ yield就会放行一次,没传参数就返回None,经循环再次停在yield g.send('狗不理包子') #egon 吃了 狗不理包子 给yield左边的变量传参 , 触发了__next__方法,执行一次然后再次循环到yield停止 g.send('饺子') #egon 吃了 饺子 再一次触发_next_方法再输出一次
4.4 yield 与 return比较
1.yield 1.帮你提供了一种自定义生成器方式 2.会帮你将函数的运行状态暂停住 3.可以返回值 2.与return之间异同点 相同点:都可以返回值,并且都可以返回多个 不同点: yield可以返回多次值,而return只能返回一次函数立即结束 yield还可以接受外部传入的值
4.5 生成器表达式 快速生成自定义迭代器
1.当遇到特别大的可迭代对象,节省内存 res = (i for i in range(1,100000000) if i != 4) # 生成器表达式,好像好像元祖生成式,可惜并不是 print(res) print(res.__next__()) #1 print(res.__next__()) #2 # 生成器不会主动执行任何一行代码,必须通过__next__触发代码的运行,要一个给一个 2.读取文件内字符长度: 1.一次读出直接统计,占内存 f = open('xxx.txt','r',encoding='utf-8') data = f.read() print(len(data))
f.close() 2.一行一行读,还可以 with open('xxx.txt','r',encoding='utf-8') as f: n = 0 for line in f: n += len(line) print(n) 3.迭代器读,真牛逼 with open('xxx.txt','r',encoding='utf-8') as f: g = (len(line) for line in f) print(sum(g))
4.5 生成器启动条件
启动生成器进行迭代有以下几种方法:
第一种:for循环,for循环的本质就是调用了iter和next方法进行了迭代
第二种:调用next方法
第三种:调用send方法
第四种:数据类型强制转换,比如使用list()强制转换。
只要没有以上四种方法进行迭代,那么生成器就没有进行运算
4.6 面试题
def add(n,i): return n+i def test(): for i in range(4): yield i g=test() #到这里初始化生成器,把函数变成生成器,但是代码一句也不会执行 for n in [1,10]: #这是一个列表,两个元素,也就是说会循环两次 g=(add(n,i) for i in g) #这是一个生成器表达式,代表一个生成器 # 第一次for循环结束g=(add(1,i) for i in test()) 到这里test()代码根本不会运行,因为根本没看到_next_ # 第二次for循环g=(add(10,i) for i in {add(10,i) for i in test()}) 到这里代码也不会执行 print(n) res=list(g)#list内部自带_next_触发g,g里面生成器逐个触发 g=(add(10,i)=20 for 10 in {add(10,0)=10 for i in test()=0}) #A. res=[10,11,12,13] #B. res=[11,12,13,14] #C. res=[20,21,22,23] 答案 #D. res=[21,22,23,24]