19-Python基础知识学习-----迭代器与生成器

迭代器与生成器

迭代器

  迭代器是用来迭代取值的工具 ,迭代是重复反馈过程的活动,每一次对过程的重复称为一次 “迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,单纯的重复并不是迭代:

while True:
    msg = input('>>: ').strip()
    print(msg)

  下面的while循环才是一个迭代过程,不仅满足重复,而且以每次重新赋值后的index值作为下一次循环中新的索引进行取值,反复迭代,最终可以取尽列表中的值:

goods=['mac','lenovo','acer','dell','sony']

index=0
while index < len(goods):
    print(goods[index])
    index+=1

可迭代对象

  通过索引的方式进行迭代取值仅适用于序列类型(如:字符串、列表、元组),对于非序列类型(如:字典、集合)则必须找到一种不依赖索引来进行迭代取值的方式,这就用到了迭代器。

  在了解迭代器之前必须先弄清楚一个很重要的概念:可迭代对象(Iterable)。从语法形式上讲, 内置有__iter__方法的对象都是可迭代对象 ,字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。

迭代器对象

  调用对象的 __iter__()方法 返回的结果就是一个迭代器对象(Iterator)。 迭代器对象是内置有__iter__和__next__方法的对象

  • __iter__方法:执行迭代器对象的__iter__()方法得到的仍然是迭代器本身
  • __next__方法:执行迭代器对象的__next__()方法就会计算出迭代器中的下一个值

  迭代器是Python提供的一种统一的、不依赖于索引的迭代取值方式,只要目标对象存在多个值,无论它是序列类型还是非序列类型都可以按照迭代器的方式取值:

s={1,2,3} # 可迭代对象s
i=iter(s)  # 本质就是在调用s.__iter__(),返回s的迭代器对象i
print(next(i)) # 本质就是在调用i.__next__()  1
print(next(i)) # 2
print(next(i)) # 3
print(next(i))  #抛出StopIteration的异常,代表无值可取,迭代结束

注意:打开的文件本身就是一个迭代器对象

  有了迭代器之后,我们便可以不依赖索引迭代取值了,使用while循环的实现方式如下:

goods=['mac','lenovo','acer','dell','sony']
i=iter(goods) #每次都需要重新获取一个迭代器对象
while True:
    try:
        print(next(i))
    except StopIteration: #捕捉异常终止循环
        break

for循环原理

  for循环又称为迭代循环,in后可以跟任意可迭代对象:

goods=['mac','lenovo','acer','dell','sony']
for item in goods:   
    print(item)

  for循环的工作原理如下:

  • 步骤1:首先会调用可迭代对象goods的内置的__iter__方法拿到一个迭代器对象
  • 步骤2:然后再调用该迭代器对象的__next__方法将取到的值赋给item,执行循环体完成一次循环
  • 重复步骤2,直到捕捉StopIteration异常,结束迭代

迭代器的优缺点

  基于索引的迭代取值,所有迭代的状态都保存在了索引中;而基于迭代器实现迭代的方式不再需要索引,所有迭代的状态就保存在迭代器中。这种处理方式优点与缺点如下:

  • 优点:
    • 为序列和非序列类型提供了一种统一的迭代取值方式
    • 惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值。就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流。对于其它容器类型(如列表),需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的
  • 缺点:
    • 除非取尽,否则无法获取迭代器的长度
    • 只能取下一个值,不能回到开始:迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值

生成器

  如果函数体里面包含 yield关键字 ,那么调用函数的时候就不会执行函数体的代码,它的返回值就是一个生成器对象:

def my_range(start,stop,step=1):
    print('start...')
    while start < stop:
        yield start
        start+=step
    print('end...')... 

g=my_range(0,3)
print(g) # <generator object my_range at 0x104105678>

# 生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器

print(g.__iter__) # <method-wrapper '__iter__' of generator object at 0x1037d2af0
print(g.__next__) # <method-wrapper '__next__' of generator object at 0x1037d2af0>

  因而我们可以用next触发生成器所对应函数的执行:

next(g) # 触发函数执行直到遇到yield则停止,将yield后的值返回,并在当前位置挂起函数start...0
next(g) # 再次调用next(g),函数从上次暂停的位置继续执行,直到重新遇到yield...1
next(g) # 周而复始...2
next(g) # 触发函数执行没有遇到yield则无值返回,即取值完毕抛出异常结束迭代end...Traceback (most recent call last):
  File "<stdin>", line 1, in <module>StopIteration

  既然生成器对象属于迭代器,那么就可以使用for循环迭代:

def countdown(num):
    print("countdown start...")
    while num > 0:
        yield num
        num -= 1
    print("countdown end...")

for i in countdown(3):
    print(i)

# countdown start...
# 3
# 2
# 1
# countdown end...

  通过yield关键字就有可以实现自定义迭代器。yield可以用于返回值,但它和return不同,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值。

yield表达式应用

  在函数内可以采用表达式形式的yield:

def eater():
    print('Ready to eat')
    while True:
        food=yield
        print('get the food: %s, and start to eat' %food)

  可以拿到函数的生成器对象持续为函数体send值:

g=eater() # 得到生成器对象
print(g) # <generator object eater at 0x101b6e2b0>>>> 
print(next(g)) # 需要事先”初始化”一次,让函数挂起在food=yield,等待调用g.send()方法为其传值Ready to eat
g.send('包子') # get the food: 包子, and start to eat
g.send('鸡腿') # get the food: 鸡腿, and start to eat

  针对表达式形式的yield,生成器对象必须事先被初始化一次,让函数挂起在food=yield的位置,等待调用g.send()方法为函数体传值。

注意:

  1. g.send(None) 相当于 next(g)
  2. g.close() 关闭之后就不能再传值了

  可以编写装饰器来完成为所有表达式形式yield对应生成器的初始化操作:

def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper

@init
def eater():
    print('Ready to eat')
    while True:
        food=yield
        print('get the food: %s, and start to eat' %food)

  表达式形式的yield也可以用于返回多次值,即 变量名=yield 值的形式:

def eater():
    print('Ready to eat')
    food_list=[]
    while True:
        food=yield food_list
        food_list.append(food)
        
e=eater()
print(next(e)) # Ready to eat[]
e.send('蒸羊羔') # ['蒸羊羔']
e.send('蒸熊掌') # ['蒸羊羔', '蒸熊掌']
e.send('蒸鹿尾儿') # ['蒸羊羔', '蒸熊掌', '蒸鹿尾儿']

列表生成式

  列表生成式是python为我们提供的一种简化代码的解决方案,用来快速生成列表,语法如下:

[expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
#...
for itemN in iterableN if conditionN]

#类似于
res=[]
for item1 in iterable1:
    if condition1:
        for item2 in iterable2:
            if condition2
                ...
                for itemN in iterableN:
                    if conditionN:
                        res.append(expression)

  比如下面的案例:

egg_list=[]
for i in range(10):
    egg_list.append('鸡蛋%s' %i)

# 用列表生成式可以一行解决
egg_list=['鸡蛋%s' %i for i in range(10)]

  下面是一个字典生成式:

keys = [1, 2, 3]

print({key:None for key in keys })  # {1: None, 2: None, 3: None}

keys = [('name', 'tom')]

print({k: v for k,v in keys if k == 'name'})    # {'name': 'tom'}

注意:同理还存在集合生成式,但是没有元祖生成式。

生成器表达式

  创建一个生成器对象有两种方式,一种是调用带yield关键字的函数,另一种就是生成器表达式:

(expression for item in iterable if condition)

  对比列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象:

print([x*x for x in range(3)]) # [0, 1, 4]

g=(x*x for x in range(3))

print(g) # <generator object <genexpr> at 0x101be0ba0>

  对比列表生成式,生成器表达式的优点是节省内存(一次只产生一个值在内存中):

print(next(g)) # 0
print(next(g)) # 1
print(next(g)) # 4
print(next(g)0 #抛出异常StopIteration

  如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成:

with open('db.txt','rb') as f:
    nums=(len(line) for line in f)
    total_size=sum(nums) # 依次执行next(nums),然后累加到一起得到结果
上一篇:[编程基础][C#]yield


下一篇:python面试题(34-44)