Loading

四:迭代器生成器

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循环的原理:获取迭代器,然后用迭代器依次获取数据,两个步骤缺一不可,否则报错.  

  1. 获取可迭代对象的迭代器,没有__iter__报错提示是不可迭代的对象
  2. 如果类里面没有__next__方法报错,
    TypeError: iter() returned non-iterator of type 'A'
  3. 1和2缺一不可,都满足了然后循环调用迭代器的__next__()方法,直到没有值抛出StopIteration异常
  4. 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)
View Code

自定义迭代器对象,内部主要完成的是__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)
View Code

主动前进的方式很少见,基本都是被动或者隐藏调用

for循环拿到迭代器,就是生成器

取值的方式:for循环,数据类型强制转换(占内存,是一次性的取所有)、next方法
生成器最后一个yield后面的代码会执行但是结果是没有办法返回的,外面拿不到。
调用next()方法,让迭代器逻辑开始执行到yield停止
g.send()比next(g)多了传值进入生成器内部的功能,第一个send(None)因为执行到一地个yield不需要传值

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))
加上装饰器

 

posted @ 2019-09-30 08:13  浅忆尘  阅读(206)  评论(0编辑  收藏  举报