首页 > 技术文章 > Python基础 ( 六 ) —— 迭代器和生成器

Matrixssy 2018-12-24 16:45 原文

#迭代器


 

#什么是迭代器协议?

是指对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,否则引起一个stop Iteration异常,以终止迭代。并且不可逆。

对象1 -->提供一个next方法 --> 调用对象的next方法(对象.next) -->对象2  ...... --> 直到无法生成下一项,引起stopIteration异常,报错

#可迭代对象

遵循迭代器协议的对象

#协议

协议是一种约定,可迭代对象实现了迭代器协议,python的一些内部工具(如for、sum、min、max函数)使用迭代器协议访问对象

#修正一下之前的一些误区

前面的章节我们都认为,(字符串、列表、元组、字典、集合、文件对象)都为可迭代对象,其实不然

他们本身均不遵循迭代器协议,不信你试试 'list'.__next__()(报错) 他们并没有 next方法,故他们不是可迭代对象

那么问题来了,为什么我们能用for循环他们呢? for循环不是使用迭代器协议访问对象吗?

其实在使用for循环时,系统内部自动的进行了一个操作,即 对象1 = 对象.__iter__() ,这样对象1就为一个迭代器(可迭代对象)来代替原来的对象用于执行for循环

然后不断执行对象.__next__(),直到发现stopIteration异常,终止。

虽然如列表可用while循环通过索引的方式遍历所有元素,但无序的字典、集合则无法索引。于是for其实给各种类型提供了一个统一的遍历手段,

text = 'hello'                            for i in text :
text_new = text.__iter__()                         --> 相当于    print(i)      里的一次迭代
print(text_new.__next__())

#用while循环模拟for循环

a = 'abcdf23'
a1 = a.__iter__()    
for i in a :
  print(i)
#相当于
while True : try: #捕捉异常 print(a1.__next__()) except StopIteration : print('迭代完毕,循环终止') break

#iter函数

将符合迭代器协议的对象转换为迭代器,其第二个参数控制迭代到什么时候为止

1 a = ['a', 'b', 'c', 'd']
2 def test():
3     return a.pop()
4 #第二个参数'b'代表一直迭代直到值为'b'的时候停止
5 b = iter(test, 'b')
6 for i in b:
7     print(i)

小补充:

a.__next__ == next(a)

迭代器就是可迭代对象,值为地址的形式


 

#生成器

#生成器可以看做是一种数据类型,本身就遵循迭代器协议

#生成器是一种特殊的迭代器

#如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)

#生成器只能遍历一次

 

def iteration() :
    for i in range(1,6) :
        yield '迭代第%s次' %(i)
a = iteration()
for item in a :
    print(item)
print(a.__next__())                 -->StopIteration

 

 

 

#生成器函数

#yield语句(可被send赋值) , 代替return (返回一个生成器,相当于自动帮你完成了__iter__) ,并且可以返回多次

#可以保留函数的状态   执行函数 --> 碰上第一个yield 返回一个生成器 ,函数暂停-->  返回的生成器执行了一个 next方法 -->  函数从暂停位置(第一个yield处)继续执行,碰上第二个yield 返回一个生成器

#好处,函数每完成一部分的数据都可以马上进行下一步处理,而不是全部完成后再一起处理

 

def a() :
    for i in range(1,10) :
        print('开始迭代第%s次' %i)
        yield '第%s次迭代完毕' %i
        print('准备下一次迭代')
b =a()
print(b.__next__())
print(b.__next__())
print(b.__next__())

 #send语句,除了有__next__()的进行下一次迭代的功能,还能给yield传值

def a() :
    for i in range(1,10) :
        print('开始迭代第%s次---->函数执行的结果' %i)
        res=yield '第%s次迭代完毕(这是返回值,函数外部print的结果)' %i
        print(res)
b =a()
print(b.__next__())              #第一次迭代只能传 None 因为它是传给上一次迭代的yield
print(b.__next__())              #yield不用send传值默认为None
print(b.send('准备下一次迭代'))

 

 

#三元表达式 

name = 'wwj'
res = 'beautiful girl' if name == 'wwj' else 'get out'
print(res)
grade1 = ['成绩 = %s' %(i) for i in range(10) if i > 5]
print(grade1) 

#列表解析(简洁的生成列表)

grade = []
for i in range(10) :
    grade.append('成绩 = %s' %(i))
print(grade)
#以下代码用列表解析来完成上文相同的功能
grade1 = ['成绩 = %s' %(i) for i in range(10)]
print(grade1)
#但是以上方法仍有不足,以上等于是将所有列表元素都加载到内存中,占用内存空间大
#以下用生成器表达式(把列表解析的[]换成()就行)
grade1 = ( '成绩 = %s' %(i) for i in range(10) if i > 5 )
print(grade1)

 #生成器表达式

 #把列表转换为生成器,节省内存空间

l = [1,2,3,4,'sdfadasf',['asdfds']]
generator = (i for i in l )
print(generator)
while True :
    try :
        print(generator.__next__())
    except StopIteration :
        print('迭代完毕')
        break

#小补充

l = [1,2,3,4]
sum(l)

实际等于

list = [1,2,3,4]
sum(i for i in list)       #所以sum()函数默认帮我们做了一个转换为生成器的操作

 生成器也能被for循环

 

def iteration() :
    for i in range(1,100) :
        yield '迭代第%s次' %(i)
for item in iteration() :
    print(item)

 

#一个生成器的小例子,计算人口百分比(比直接获取整个人口普查文本然后放入列表中要节省内存空间的多)

#'人口普查'文本

{'地区':'北京' , '人口' : 10232142}
{'地区':'新疆' , '人口' : 102322}
{'地区':'青岛' , '人口' : 3223322}

#获取地区及其人口所占百分比

def population() :
    with open('人口普查','r',encoding='utf-8') as f :
        # num = 0
        # for i in f :
        #     num += eval(i)['人口']
        num = sum(eval(i)['人口'] for i in f)     #比上几行的代码简洁优美
        f.seek(0)                                #此时生成器已经遍历完,需要seek返回文章开头重新遍历
        for i in f :
            a = eval(i)['地区']
            b = eval(i)['人口']
            yield a,"人口百分比 %%%f"%(b/num*100)
a = population()
print(a.__next__())
print(a.__next__())
print(a.__next__())

 #产生和处理同时完成(同时执行两个程序),单线程里的并发

def consumer(num):                  #获取产生的值,并进行处理
    print('%s 开始照镜子' %num)
    while True:
        res=yield
        print('偷偷对%s使用变美喷雾 颜值+%d' %(num,res))
        if res==5 :
            break
def producer(name):                  #产生值
    b = consumer('%s' % name)
    b.__next__()
    for i in range(1,6):
        try:
            b.send(i)
        except StopIteration :
            print('wwj说:哇,我怎么这么美')
            break
producer('wwj')

 

 

 

 

 

 

 

 

推荐阅读