四:迭代器生成器
1.迭代器
迭代和重复是两回事,迭代是重复的进行反馈,每次迭代的结果作为下一次迭代的初始值。
while True:死循环是不断的重复不是迭代。
while inde<5:index++,index每次迭代时index都有变化,这才是迭代,来达到满意的结束条件或结果。
1.1循环获取或者生成数据。根据前一个值获取下一个,根据前一个值生成下一个值。
1.2有__iter__()方法的对象就是可迭代对象Iterable,可以被for循环,为了更方便的获取容器对象里面的数据。
1.3有__iter__()和__next__()方法的对象是迭代器对象,迭代器可以向后移动指针。
__iter__()获得迭代器,__next__()计算出迭代器的下一个值。
迭代器的出现可以让你不需要依赖索引,这样可以不适用索引迭代没有索引的容器,还可以迭代有索引的容器。
for循环也叫迭代循环
for i in iterable:
1.4for循环的原理:获取迭代器,然后用迭代器依次获取数据,两个步骤缺一不可,否则报错.
- 获取可迭代对象的迭代器,没有__iter__报错提示是不可迭代的对象
- 如果类里面没有__next__方法报错,
TypeError: iter() returned non-iterator of type 'A'
- 1和2缺一不可,都满足了然后循环调用迭代器的__next__()方法,直到没有值抛出StopIteration异常
- for循环抓住此异常,偷偷处理了,外部无感知
优点:不需要索引,内存中一次只有一个数据
缺点:除非取尽否则不知道长度,只能向后走,不能回头,想再迭代只能重新获取迭代器
1.5自定义迭代器:借助已经存在的可迭代数据结构,重写__next__方法返回数据,几乎不会这样写!
class Students: stu_list=[] position = 0 def __init__(self,*args): for name in args: self.stu_list.append(name) def __iter__(self): return self def __next__(self): if self.position < len(self.stu_list): item = self.stu_list[self.position] self.position+=1 return item raise StopIteration() def append_stu(self,student): self.stu_list.append(student) stus = Students("张三","李四","王五") stus.append_stu("老王") for i in stus: print(i)
自定义迭代器对象,内部主要完成的是__next__方法如何拿到数据.
很少自己写迭代器,因为给你的数据类型完全够用,且__next__用c语句已经写好了内部逻辑,可以不依赖索引来获取数据十分方便,就连列表也不用索引迭代获取数据了.
总结:迭代器里面多了三个方法
iter_l = [1,2,3,4,5,6].__iter__() #获取迭代器中元素的长度 print(iter_l.__length_hint__()) #根据索引值指定从哪里开始迭代 print('*',iter_l.__setstate__(4)) #一个一个的取值 print('**',iter_l.__next__()) print('***',iter_l.__next__())
可见列表的迭代器底层用的索引在迭代数据,只是对调用者隐藏了细节,只知道调用__next__就可以获取下一个值.
字符串.列表.元组.都有下标,set.dict没有下标,迭代器把无论是否有下标的数据类型迭代数据全部进行了统一,这样用for就可以轻松遍历他们,很舒服.
2.生成器
生成器函数和生成器表达式,本质就是特殊的迭代器。因此for可以迭代生成器
从名字上就可以猜出来,生成器主要用来生成数据,迭代器主要用来迭代已有的数据。
迭代器只有取尽才停,生成器函数可以控制停的点。
常用来作为生成数据的工具,本质是迭代器,自带了__iter__和__next__不用我们实现了,只需要关注生成逻辑即可.
往往生成的数据量很大,防止内存被占满,常常使用生成器
def num_gen(): num=0 while True: yield num num+=1 g = num_gen() for i in g: print(i)
惰性运算,开发者自定义
2.1生成数据的逻辑一致------------生成器表达式
类似于列表推导,但是生成器返回的结果是一个可迭代对象generator,而不是一次构建一个结果列表
2.2生成数据的逻辑随着条件不同产生数据不同---------------生成器函数
常规函数定义,但是,使用yield语句而不是return语句返回结果。
yield的作用:1返回值,2挂起函数状态
yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
生成器函数调用内部逻辑不会执行,而是得到一个对象,里面保存着生成数据的逻辑,给一鞭子__next__()才会走一步,直到抛出StopIteration异常
def gen_test(): for i in range(20): yield "test%s" % i g = gen_test() #调用生成器函数得到生成器,只有间接或直接调用__next__()才会执行生成器函数 #执行到下一个yield #迭代生成器不断的从yield返回值到全局,实现一边生产一边获取值 for i in g: print(i)
主动前进的方式很少见,基本都是被动或者隐藏调用
for循环拿到迭代器,就是生成器
for i in range(5):
print(i)
用的就是生成器,只不过索引赋予了含义就是生成器中产生的值.
def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count g_avg = averager() next(g_avg) print(g_avg.send(10)) print(g_avg.send(30)) print(g_avg.send(5))
def init(func): #在调用被装饰生成器函数的时候首先用next激活生成器 def inner(*args,**kwargs): g = func(*args,**kwargs) next(g) return g return inner @init def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count g_avg = averager() # next(g_avg) 在装饰器中执行了next方法 print(g_avg.send(10)) print(g_avg.send(30)) print(g_avg.send(5))