Python迭代器和生成器详解(包括yield详解)
文章目录一、迭代器1.可迭代对象(Iterable)2.迭代器对象(Iterator)3.for循环原理4.迭代器的优缺点二、生成器1.yield 原理2.yield 和 return 区别3.yield表达式应用4.生成器的用处三、三元表达式、列表生成式、生成器表达式1.三元表达式2.三元表达式3.生成器表达式结语一、迭代器迭代器即用来迭代取值的工具。迭代器是一个可以记住遍历的位置的对象。迭代器
文章目录
一、迭代器
迭代器即用来迭代取值的工具,是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。
迭代器只能往前不能后退。
1. 可迭代对象(Iterable)
通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串,列表,元组。
对于没有索引的字典、集合等非序列类型,必须找到一种不依赖索引来进行迭代取值的方式,这就用到了迭代器。
字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。
迭代器对象可以使用常规for语句进行遍历,也可以使用 next() 函数。
2. 迭代器对象(Iterator)
迭代器对象是内置有iter和next方法的对象,打开的文件本身就是一个迭代器对象。
执行迭代器对象 .iter() 方法得到的仍然是迭代器本身。
执行迭代器 .next() 方法就会计算出迭代器中的下一个值。
3. for 循环原理
有了迭代器后,便可以不依赖索引迭代取值了,使用while循环的实现方式如下:
goods=['mac','lenovo','acer','dell']
i=iter(goods) #每次都需要重新获取一个迭代器对象
while True:
try:
print(next(i))
except StopIteration: #捕捉异常终止循环
break
for循环又称为迭代循环,in后可以跟任意可迭代对象,上述while循环可以简写为:
goods=['mac','lenovo','acer','dell']
for item in goods:
print(item)
for 循环在工作时:
首先会调用可迭代对象goods内置的iter方法拿到一个迭代器对象,
然后再调用该迭代器对象的next方法将取到的值赋给item,执行循环体完成一次循环,
周而复始,直到捕捉StopIteration(停止迭代),结束迭代。
4. 迭代器的优缺点
优点
1、为序列和非序列类型提供了一种统一的迭代取值方式。
2、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值。
迭代器同一时刻在内存中只有一个值,因而可以存放无限大的数据流。
而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。
缺点
1、除非取尽,否则无法获取迭代器的长度。
2、只能取下一个值,不能回到开始。
迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next。
若是要再次迭代同个对象,只能重新调用iter方法创建一个新的迭代器对象。
如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。
二、生成器
使用了 yield 的函数被称为生成器(generator)。
生成器是一个返回迭代器的函数,只能用于迭代操作,可理解为生成器就是一个自定义迭代器。
1. yield 原理
yield 能够临时挂起当前函数,记下其上下文(包括局部变量、待决的 try catch 等),将控制权返回给函数调用者。
当下一次再调用其所在生成器时,会恢复保存的上下文,继续执行剩下的语句,直到再遇到 yield 或者退出为止。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值,并在下一次执行 next() 方法时从当前位置继续运行。
2. yield 和 return 区别
yield可以用于返回值,但不同于return。
函数一旦遇到return就结束了,销毁上下文(弹出栈帧),将控制权返回给调用者。
而yield可以保存函数的运行状态,挂起函数,用来返回多次值。
因此,以 yield 进行执行流控制的函数称为生成器函数,以 return 进行执行流控制的函数就是普通函数。
由于可以临时挂起函数的执行,yield 可以充当其调用者和被挂起函数间交互的桥梁。
3. 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() # 得到生成器对象
>>> g
<generator object eater at 0x101b6e2b0>
>>> next(e) # 需要事先”初始化”一次,让函数挂起在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)。
可以编写装饰器来完成为所有表达式形式 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()
>>> next(e)
Ready to eat
[]
>>> e.send('蒸羊羔')
['蒸羊羔']
>>> e.send('蒸熊掌')
['蒸羊羔', '蒸熊掌']
>>> e.send('蒸鹿尾儿')
['蒸羊羔', '蒸熊掌', '蒸鹿尾儿']
4. 生成器的优点
- 精简代码
使用 yield 关键字或者生成器表达式可以很方便的生成一个迭代器对象。
为了说明这一点,我们来比较一下对于一个需求的不同实现。
该需求很简单:获取前 n 个自然数。
1、构造一个数组然后返回。当 n 很小的时候,该实现没有什么问题,但是当 n 变得很大,内存是吃不消的。
2、用生成器模式,不用 yield。构造一个对象,并实现__iter__ 和 __next__方法。但为了实现一个简单的需求却不得不构造冗长的代码。
3、使用了 yield 构造一个生成器函数
# 产生项目而不是返回列表的生成器
def firstn(n):
num = 0
while num < n:
yield num
num += 1
sum_of_first_n = sum(firstn(1000000))
- 提高性能
这一条主要是针对内存使用上来说的。
因为迭代器不会保存所有值,而是在运行中动态的计算出数列的各个值,并将之前的数值扔掉。
这里举个实例。
假设我们有一个很大的文件(比如说 16G ) ,但是你的电脑只有 8G 内存,你如何利用 Python 对其进行处理?
答案是使用 yield 构造生成器。
def read_by_chunks(file, chunk_size=1024):
while True:
data = file.read(chunk_size)
if not data:
break
yield data
f = open('your_big_file.dat')
for chunk in read_by_chunks(f):
process_chunk(chunk)
这种通过构造生成器逐块读取的方法叫做惰性加载,也叫流式读取,是处理大文件的一种常见方式。
惰性加载也被运用在软件设计和网页设计当中,其特征为用户通过鼠标,滚动浏览页面,直到页面下方时,就会自动加载更多内容。
如果你的是文本文件,可以按行读取,那代码就更简单了:
with open('your_big_file.txt') as f:
for line in f:
process_line(line)
Python文件句柄(file handle)就是一个迭代器,默认会将文件按行分割以惰性加载。
结语
以上这些,就是Python迭代器与生成器的基础知识,希望对大家有所帮助。 如果大家有任何疑问请给我留言,我会尽快回复大家。在此也非常感谢大家对CSDN的支持!开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)