可迭代对象和迭代器常用于for循环。基本的概念是:
- 迭代:代表一个重复的过程,每一次重复都是基于上一次结果而来。
- 可迭代对象:凡内置__iter__方法的对象,都称为可迭代对象,如str,list,tuple,dict,set,文件对象;
- 迭代器对象:既内置__iter__方法(执行后返回迭代器本身)又内置__next__方法的对象,执行**next方法**可以不依赖索引取值;
- 生成器对象:包含关键字**yield**的函数,函数调用不会执行内部代码,但返回一个值,即生成器对象(即,首次调用函数时,返回生成器对象);
- 迭代器一定是可迭代对象,可迭代对象不一定是迭代器对象,文件对象本身就是一个迭代器对象。
迭代器一般适用于这些应用场景:1.不关心元素的随机访问;2.序列中元素的个数不可确定。其特点是:
- 迭代器优点: 1.提供一种通用的不依赖索引的迭代取值方式;2.同一时刻内存中只存在一个值,节省内存;
- 迭代器缺点: 取值不如按照索引的方式灵活,不能取指定位置的值,且只能往后取,不能往前取。
我们最常见的for循环,其本质就是迭代器循环(ps:但凡能被for循环取值的对象就是可迭代对象)。其基本步骤是:
1.先调用关键字in之后的对象的__iter__方法,将其变成一个迭代器对象;
2.调用next(迭代器),将得到的返回值赋值给变量名;
3.循环往复直到next(迭代器)抛出异常,for会自动捕捉异常然后结束循环;
- yield: 1.yield只能在函数内使用,可以保存函数的暂停状态,实现了一种自定义的迭代器;
2.对比return,区别在于多个yield可以多次返回值,而return只能返回一次(函数末尾)。
一、可迭代对象
在Python中,对list、tuple、str等类型的数据可以使用for...in...的循环语法,从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。
在Python的基本数据类型中,列表、元组、字符串、字典都是可迭代的,而整数、浮点数、布尔数都是不可迭代的。
list_a = [1, 2, 3]
for a in list_a:
print(a, end=' ')
tuple_b = ('a', 'b', 'c')
for b in tuple_b:
print(b, end=' ')
str_c = 'AKQJ'
for c in str_c:
print(c, end=' ')
dict_d = {'BJ': '北京', 'SH': '上海', 'GZ': '广州', 'SZ': '深圳'}
for d in dict_d:
print(d, end=' ')
运行结果:
1 2 3 a b c A K Q J BJ SH GZ SZ
在Python中,把可以通过for...in...这类语句迭代,读取一条数据供我们使用的对象称之为可迭代对象(Iterable)。列表、元组、字符串、字典都是可迭代对象。可以使用 isinstance() 判断一个对象是否是 Iterable 对象。
from collections.abc import Iterable
print(isinstance(list_a, Iterable))
二、可迭代对象的本质
对可迭代对象进行迭代使用的过程,每迭代一次(即在for...in...中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。
在这个过程中,我们需要知道每次访问到了第几条数据,以便下一次迭代返回的是下一条数据,不会跳过或者重复返回数据。Python帮我们实现了这个功能,这个功能就是迭代器(Iterator)。
可迭代对象的本质就是提供一个迭代器帮助我们对其进行迭代遍历使用。那Python是怎么实现这些功能的呢?
在Python中,可迭代对象通过__iter__方法向我们提供一个迭代器,在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据。
可迭代对象是一个具备了__iter__方法的对象,通过__iter__方法获取可迭代对象的迭代器。
三、iter()函数与next()函数
列表、元组、字符串、字典等都是可迭代对象,我们可以通过iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据。
list_b = ['ppp', 'yyy', 'ttt', 'hhh', 'ooo', 'nnn']
iterator_b = iter(list_b)
print(next(iterator_b))
print(next(iterator_b))
print(next(iterator_b))
print(next(iterator_b))
print(next(iterator_b))
print(next(iterator_b))
运行结果:
ppp
yyy
ttt
hhh
ooo
nnn
iter(iterable)函数是把可迭代对象的迭代器取出来,内部是调用可迭代对象的__iter__方法,来取得迭代器的。
next(iterator)函数是通过迭代器取得下一个位置的值,内部是调用迭代器对象的__next__方法,来取得下一个位置的值。
当我们已经迭代完最后一个数据之后,再次调用next()函数会抛出StopIteration的异常,来告诉我们所有数据都已迭代完成,不用再执行next()函数了。
for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。
四、迭代器Iterator
通过上面的分析,我们已经知道,迭代器用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。
在使用next()函数的时候,调用的是迭代器对象的__next__方法。所以,我们要想构造一个迭代器,就要实现它的__next__方法。
同时,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可。
也就是说,一个实现了__iter__方法和__next__方法的对象,就是迭代器,迭代器自身也是一个可迭代对象。
五、自定义迭代器
迭代器最核心的功能就是可以通过next()函数的调用来返回下一个数据值。
如果每次返回的数据值不是在一个已有的数据集合中,而是通过程序按照一定的规律计算生成的,那就不用再依赖一个已有的数据集合,也就是说不用再将所有要迭代的数据都一次性缓存下来,这样可以节省大量的存储(内存)空间。
class FeiboIterator(object):
"""斐波那契数列迭代器"""
def __init__(self, n):
# 斐波那数列值的个数
self.n = n
# 记录当前遍历的下标
self.index = 0
# 斐波那数列前面的两个值
self.num1 = 0
self.num2 = 1
def __next__(self):
"""被next()函数调用来获取下一个数"""
if self.index < self.n:
num = self.num1
self.num1, self.num2 = self.num2, self.num1 + self.num2
self.index += 1
return num
else:
raise StopIteration
def __iter__(self):
"""迭代器的__iter__返回自身即可"""
return self
if __name__ == '__main__':
fb = FeiboIterator(20)
for num in fb:
print(num, end=' ')
运行结果:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181
上面的代码中,我们自定义一个类,里面实现了__next__和__iter__方法,__next__方法中每次返回的值是我们通过计算得到的结果,所以可以一直使用next()方法。
当我们通过for...in...循环来遍历迭代斐波那契数列中的前n个数时,会在第n+1次调用next()时抛出StopIteration异常,然后结束for循环,这与Python迭代器的功能是一样的。