迭代器与生成器
迭代器
迭代器是用来迭代取值的工具 ,迭代是重复反馈过程的活动,每一次对过程的重复称为一次 “迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,单纯的重复并不是迭代:
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()方法为函数体传值。
注意:
- g.send(None) 相当于 next(g)
- 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),然后累加到一起得到结果