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]

 

  

 

 

 

 

  

posted @ 2019-07-15 22:44  www.pu  Views(268)  Comments(0Edit  收藏  举报