1. 迭代器
1.1 可迭代对象
可迭代对象指的是实现了 __iter__ 方法的对象,可以被 for 循环遍历的容器,比如一个列表,自定义的链表等。使用 iter() 方法获取它的迭代对象 (可以理解为链表的指针)。
1.2 迭代器
迭代器是指实现了 __iter__ 和 __next__ 方法的对象。__iter__ 返回它本身,确保每个单独的节点都是可以被迭代的,满足链表可以从第 n 个元素开始访问的需求;__next__ 返回它的下一个节点,如果已经遍历完毕则抛出 StopIteration 异常。
# 实现了__iter__方法,是一个可迭代对象,可以理解为是一个整的链表,但此时还不能单独访问其中的元素
class Node:
def __init__(self, data):
self.data = data
self.next: Node | None = None
def __iter__(self):
return NodeIter(self)
# 是迭代器对象,可以理解为指向链表首元素的指针,通过这个指针遍历所有的链表元素
class NodeIter:
def __init__(self, node: Node = None):
self.current = node
def __iter__(self):
return self
def __next__(self):
if self.current is None:
raise StopIteration
else:
node = self.current
self.current = self.current.next
return node
1.3 可迭代对象和迭代器的区别
- 协议不同:可迭代对象只需实现
__iter__(),迭代器需要实现__iter__()和__next__() - 状态:迭代器有内部状态(当前位置),可迭代对象通常没有
- 重用性:可迭代对象可多次遍历(每次获取新迭代器),迭代器遍历一次后耗尽
- 包含关系:所有迭代器都是可迭代对象,但并非所有可迭代对象都是迭代器
依旧以链表举例,整个链表是一个可迭代对象,遍历这个对象的时候实际上是通过一个指向链表首元素的指针不断地移动到下一个元素来完成的,这个指针就可以理解为迭代器。
2. 生成器
2.1 概念
生成器是一种特殊的迭代器,它通过 yield 关键字动态地返回值,而非一次性返回所有值,适合用来读取大文件或产生无限序列。生成器内部自动实现了 __iter__ 和 __next__。
当一个函数内部使用了 yield 关键字时,那么它就是一个生成器函数,此时函数的返回值不再是 return 后的返回值,而是类似于函数的对象:
<generator object fibonacci at 0x0000026E4B6D20A0>
以一个生成 n 个斐波那契的生成器举例:
def fibonacci(limit):
"""生成斐波那契数列"""
a, b = 0, 1
count = 0
while count < limit:
yield a
a, b = b, a + b
count += 1
当执行下面的代码时:
fibo = fibonacci(10)
print(fibo) # 输出:<generator object fibonacci at 0x0000026E4B6D20A0>
print(next(fibo)) # 输出 0
print(next(fibo)) # 输出 1
print(next(fibo)) # 输出 1
print(next(fibo)) # 输出 2
fibo 是一个生成器对象,这个对象可以生成 10 个斐波那契数,但不是同时生成在内存中,而是一个 next() 取回一个值,这个特性被称作'惰性加载'。执行第一个 next() 时,返回第一个 yield 后的值并且暂停函数的执行,等到第二次执行 next() 时从第一个 yield 后开始继续执行,也就是说,当我在第一个 yield 下面抛出异常,函数执行一次 next() 并不会报错,因为抛出异常语句并没有执行,第二次执行 next() 才会报错,比如下面的代码:
def generator1():
print('第一个 yield 之前的语句块')
yield 1
print('第一个 yield 之后的语句块')
print(1 / 0)
print('第二个 yield 之前的语句块')
yield 2
print('第二个 yield 之后的语句块')
ge = generator1()
print(next(ge)) # 只执行 print(1 / 0) 之前的代码函数就被中断了,所以不会报错
print(next(ge)) # 回到第一个 yield 之后的代码继续执行,执行到 print(1 / 0) 语句,报错
如果生成器函数中有 return 语句,return 语句依旧有结束函数运行的作用,return 之后的语句不可达,但仍然遵循上面的中断原则,并且 return 后的值是抛出 StopIteration 异常时传递给调用者的值。
2.2 yield from
当我们的生成器的数据是从一个列表中取得并且每次返回一个元素时应该怎么写?
def generator():
user_list = ["zhang", "li", "wang", "qian"]
for username in user_list:
yield username
ge = generator()
for i in ge:
print(i)
yield from 就是简化上面的流程,提升生成器的效率,以上代码可以用下面的代码替代:
def generator():
user_list = ["zhang", "li", "wang", "qian"]
yield from user_list
ge = generator()
for i in ge:
print(i)
