迭代器即用来迭代取值的工具。迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不能后退。
通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串,列表,元组。
对于没有索引的字典、集合等非序列类型,必须找到一种不依赖索引来进行迭代取值的方式,这就用到了迭代器。
字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。
迭代器对象可以使用常规for语句进行遍历,也可以使用 next() 函数。
迭代器对象是内置有iter和next方法的对象,打开的文件本身就是一个迭代器对象。
执行迭代器对象 .iter() 方法得到的仍然是迭代器本身。
执行迭代器 .next() 方法就会计算出迭代器中的下一个值。
有了迭代器后,便可以不依赖索引迭代取值了,使用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(停止迭代),结束迭代。
优点
1、为序列和非序列类型提供了一种统一的迭代取值方式。
2、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值。
迭代器同一时刻在内存中只有一个值,因而可以存放无限大的数据流。
而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。
缺点
1、除非取尽,否则无法获取迭代器的长度。
2、只能取下一个值,不能回到开始。
迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next。
若是要再次迭代同个对象,只能重新调用iter方法创建一个新的迭代器对象。
如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。
使用了 yield 的函数被称为生成器(generator)。
生成器是一个返回迭代器的函数,只能用于迭代操作,可理解为生成器就是一个自定义迭代器。
yield 能够临时挂起当前函数,记下其上下文(包括局部变量、待决的 try catch 等),将控制权返回给函数调用者。
当下一次再调用其所在生成器时,会恢复保存的上下文,继续执行剩下的语句,直到再遇到 yield 或者退出为止。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值,并在下一次执行 next() 方法时从当前位置继续运行。
yield可以用于返回值,但不同于return。
函数一旦遇到return就结束了,销毁上下文(弹出栈帧),将控制权返回给调用者。
而yield可以保存函数的运行状态,挂起函数,用来返回多次值。
因此,以 yield 进行执行流控制的函数称为生成器函数,以 return 进行执行流控制的函数就是普通函数。
由于可以临时挂起函数的执行,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('蒸鹿尾儿')
['蒸羊羔', '蒸熊掌', '蒸鹿尾儿']
使用 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为我们提供的一种简化代码的解决方案,语法如下
res = 条件成立时返回的值 if 条件 else 条件不成立时返回的值
针对下述场景
def max2(x,y):
if x > y:
return x
else:
return y
res = max2(1,2)
用三元表达式可以一行解决
x=1
y=2
res = x if x > y else y # 三元表达式
列表生成式是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)]
创建一个生成器对象有两种方式,一种是调用带yield关键字的函数,另一种就是生成器表达式。
与列表生成式的语法格式相同,只需要将[]换成(),即:
(expression for item in iterable if condition)
对比列表生成式返回的是一个列表,生成器表达式返回的是一个生成器对象。
>>> [x*x for x in range(3)]
[0, 1, 4]
>>> g=(x*x for x in range(3))
>>> g
<generator object <genexpr> at 0x101be0ba0>
如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式完成。
with open('db.txt','rb') as f:
nums=(len(line) for line in f)
total_size=sum(nums) # 依次执行next(nums),然后累加到一起得到结果