Python是一门被多层语法糖包装的编程语言,用户使用起来容易上手。但若不了解其底层机制,就无法精通其语言。最近研究了Python中的迭代器和生成器
迭代是访问集合元素的一种方式,在Python中,迭代是通过 for … in … 语句来完成的。
在Python中,可直接作用于 for循环 的对象都称为可迭代对象(Iterable),而可以作用于 for循环 的数据类型有以下两类:
注意:可迭代对象不一定是迭代器,但迭代器一定是可迭代对象。他们是包含关系。
str、list、tuple、dict、set 都是可迭代对象(Iterable),但它们并不是迭代器(Iterator),我们可以通过迭代器中的 iter() 函数把这些可迭代对象(Iterable) 变成 迭代器(Iterator)。
from collections import Iterable
from collections import Iterator
# int类型
print(isinstance(123, Iterable)) # 不是可迭代对象,输出:False
# str类型
print(isinstance("123", Iterable)) # 是可迭代对象,输出:True
print(isinstance("123", Iterator)) # 不是迭代器,输出:False
print(isinstance(iter("123"), Iterator)) # 变成了迭代器,输出:True
# list类型
print(isinstance([1, 2, 3], Iterable)) # 是可迭代对象,输出:True
print(isinstance([1, 2, 3], Iterator)) # 不是迭代器,输出:False
print(isinstance(iter([1, 2, 3]), Iterator)) # 变成了迭代器,输出:True
# tuple类型
print(isinstance((1, 2, 3), Iterable)) # 是可迭代对象,输出:True
print(isinstance((1, 2, 3), Iterator)) # 不是迭代器,输出:False
print(isinstance(iter((1, 2, 3)), Iterator)) # 变成了迭代器,输出:True
# dict类型
print(isinstance({"a": 1, "b": 2, "c": 3}, Iterable)) # 是可迭代对象,输出:True
print(isinstance({"a": 1, "b": 2, "c": 3}, Iterator)) # 不是迭代器,输出:False
print(isinstance(iter({"a": 1, "b": 2, "c": 3}), Iterator)) # 变成了迭代器,输出:True
# set类型
print(isinstance({1, 2, 3}, Iterable)) # 是可迭代对象,输出:True
print(isinstance({1, 2, 3}, Iterator)) # 不是迭代器,输出:False
print(isinstance(iter({1, 2, 3}), Iterator)) # 变成了迭代器,输出: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)) #
print(a) # [0, 1, 2, 3, 4]
b = (i for i in range(5))
print(type(b)) #
print(b) # at 0x0000022A6719E7C8>
从上面可以看到,我们可以直接打印出列表对象的所有元素,但无法直接打印出生成器的所有元素。如果需要打印输出元素,我们可以使用 next() 函数,或者通过 for 循环来完成。
b = (i for i in range(5))
for i in b:
print(i, end=" ") # 输出:0 1 2 3 4
def demo1(n):
return n
def demo2(n):
yield n
a = demo1(100)
print(type(a)) #
b = demo2(100)
print(type(b)) #
生成器函数和普通函数在执行流程上是有点区别的。对于普通函数,按顺序执行时遇到 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 生成器来灵活控制读取,防止内存占用过大。
参考链接:https://www.jianshu.com/p/f84bda107146