Python中的迭代器和生成器
今天我们来学习下Python中的迭代器和生成器。
迭代和可迭代对象
在学习迭代器之前,我们需要了解下迭代和可迭代对象的概念。
迭代
迭代是访问集合元素的一种方式,在Python中,迭代是通过 for ... in ...
语句来完成的。
可迭代对象
在Python中,可直接作用于 for
循环 的对象都称为可迭代对象(Iterable),而可以作用于 for
循环 的数据类型有以下两类:
- 一类是集合数据类型,比如常见的
str
、list
、tuple
、dict
、set
等 - 另一类是生成器(generator),包括生成器和带
yield
的生成器函数
我们可以通过 isinstance()
来判断一个对象是否是可迭代对象:
from collections import Iterable
print(isinstance(123, Iterable)) # int类型,输出:False
print(isinstance("123", Iterable)) # str类型,输出:True
print(isinstance([1, 2, 3], Iterable)) # list类型,输出:True
print(isinstance((1, 2, 3), Iterable)) # tuple类型,输出:True
print(isinstance({"a": 1, "b": 2, "c": 3}, Iterable)) # dict类型,输出:True
print(isinstance({1, 2, 3}, Iterable)) # set类型,输出:True
迭代器
在Python中,可以被 next()
函数调用并不断返回下一个值的对象称为迭代器(Iterator),我们可以通过 isinstance()
来判断一个对象是否是迭代器:
from collections import Iterator
print(isinstance("123", Iterator)) # str类型,输出:False
print(isinstance([1, 2, 3], Iterator)) # list类型,输出:False
print(isinstance((1, 2, 3), Iterator)) # tuple类型,输出:False
print(isinstance({"a": 1, "b": 2, "c": 3}, Iterator)) # dict类型,输出:False
print(isinstance({1, 2, 3}, Iterator)) # set类型,输出:False
从上面结果可以看到,虽然 str
、list
、tuple
、dict
、set
都是可迭代对象(Iterable),但它们并不是迭代器(Iterator),我们可以通过迭代器中的 iter()
函数把这些可迭代对象(Iterable)变成迭代器(Iterator)。
from collections import Iterator
print(isinstance(iter("123"), Iterator)) # str类型,输出:True
print(isinstance(iter([1, 2, 3]), Iterator)) # list类型,输出:True
print(isinstance(iter((1, 2, 3)), Iterator)) # tuple类型,输出:True
print(isinstance(iter({"a": 1, "b": 2, "c": 3}), Iterator)) # dict类型,输出:True
print(isinstance(iter({1, 2, 3}), Iterator)) # set类型,输出:True
迭代器中有2个基本方法:iter()
、next()
,使用iter()
创建一个迭代器后,就可以通过 next()
获取迭代器的下一个值,如果通过 next()
不断调用并返回下一个值,那么等到最后没有下一个值了,就会抛出异常:StopIteration
。
a = [1, 2, 3]
iter_a= iter(a) # 创建迭代器对象
print(next(iter_a)) # 输出:1
print(next(iter_a)) # 输出:2
print(next(iter_a)) # 输出:3
print(next(iter_a)) # iter_a没有下一个值了,报错:StopIteration
在Python中,我们经常使用的 for
循环 本质上就是通过不断调用 next()
实现的。
a = [1, 2, 3]
for i in a:
print(i)
从上面结果可以看到,我们使用 for
循环 并不会抛出异常,这是因为其使用了 try ... except ...
语句,当遇到出现 StopIteration
就退出循环,其可看作等价于下面的代码实现:
a = [1, 2, 3]
iter_a = iter(a) # 创建迭代器对象
while True:
try:
print(next(iter_a)) # 输出迭代器的下一个值
except StopIteration: # 遇到 StopIteration 就退出循环
break
我们还可以用迭代器去实现斐波那契数列:
class Fib:
def __init__(self, n):
self.n = n
self.pre = 0
self.cur = 1
def __iter__(self):
return self
def __next__(self):
if self.n > 0:
value = self.cur
self.pre, self.cur = self.cur, self.pre + self.cur
self.n -= 1
return value
else:
raise StopIteration
fib = Fib(11)
for i in fib:
print(i, end=" ") # 输出:1 1 2 3 5 8 13 21 34 55 89
生成器
在Python中,生成器(generator)也是用在迭代操作中,其本质上可以理解为一个特殊的迭代器,生成器具有和迭代器一样的特性,但它们在实现方式上不一样。我们可以通过两种不同的方式创建生成器:生成器表达式、生成器函数。
- 生成器表达式
生成器表达式和列表推导式差不多,我们只需要把列表推导式的 []
改为 ()
,这样就是一个生成器表达式了。需要注意的是,列表推导式返回的是一个列表对象,而生成器表达式返回的是一个生成器对象,因此我们可以通过生成器表达式来创建一个生成器。
a = [i for i in range(5)]
print(type(a)) # <class 'list'>
print(a) # [0, 1, 2, 3, 4]
b = (i for i in range(5))
print(type(b)) # <class 'generator'>
print(b) # <generator object <genexpr> at 0x0000022A6719E7C8>
从上面可以看到,我们可以直接打印出列表对象的所有元素,但无法直接打印出生成器的所有元素。如果需要打印输出元素,我们可以使用 next()
函数,或者通过 for
循环来完成。
b = (i for i in range(5))
for i in b:
print(i, end=" ") # 输出:0 1 2 3 4
- 生成器函数
在Python中,普通函数一般通过 return
来返回一个值,当我们使用关键字 yield
来返回值,那么这个带有 yield
的函数就变成了生成器函数。
def demo1(n):
return n
def demo2(n):
yield n
a = demo1(100)
print(type(a)) # <class 'int'>
b = demo2(100)
print(type(b)) # <class 'generator'>
生成器函数和普通函数在执行流程上是有点区别的。对于普通函数,按顺序执行时遇到 return
或最后一行函数语句就会返回;对于有 yield
的生成器函数,每次调用 next()
方法遇到 yield
语句才返回,如果再次调用 next()
方法,那么就会从上次返回的 yield
语句位置继续执行,请看下面的例子:
def demo3():
print("aaa")
yield 1
print("bbb")
yield 2
print("ccc")
yield 3
g = demo3()
next(g) # 输出:aaa
next(g) # 输出:bbb
next(g) # 输出:ccc
next(g) # 没有下一个值了,报错:StopIteration
生成器是一个特殊的迭代器,它使用起来更简单,代码更简洁。我们可以用生成器去实现斐波那契数列:
def fib(n):
pre, cur = 0, 1
while n > 0:
yield cur
pre, cur = cur, pre + cur
n -= 1
f = fib(11)
for i in f:
print(i, end=" ") # 输出:1 1 2 3 5 8 13 21 34 55 89
最后,生成器还可以用于处理海量数据的场景,比如读取大文件时,如果直接一次性读取可能会导致内存溢出,这个时候我们就可以借助 yield
生成器来灵活控制读取,防止内存占用过大。