Python 迭代器、生成器及相关表达式详解
1. 迭代器 (Iterator)
1.1 核心概念
迭代器是 Python 中用于迭代操作(即遍历容器元素)的核心工具。它基于迭代器协议,这是一个使对象可迭代的约定。
1.2 迭代器协议
一个对象要成为迭代器,必须实现两个方法:
-
__iter__(self): 返回迭代器对象自身。这是iter()函数所调用的方法。 -
__next__(self): 返回容器中的下一个元素。这是next()函数所调用的方法。当没有更多元素可供返回时,必须抛出StopIteration异常来终止迭代。
1.3 详细解析与示例
示例:
class Countdown:
def __init__(self, start):
# 初始化状态:起始计数点
self.current = start # 更改变量名以增加可读性,self.start 容易被误解
def __iter__(self):
# 返回迭代器自身。for 循环首先会调用此方法获取迭代器。
return self
def __next__(self):
# 核心逻辑:计算并返回下一个值
if self.current <= 0:
# 终止条件:抛出 StopIteration 告知循环结束
raise StopIteration
value = self.current
self.current -= 1 # 更新内部状态,为下一次调用 __next__ 做准备
return value
# 使用迭代器
print("手动调用 next():")
counter = Countdown(3)
print(next(counter)) # 输出: 3
print(next(counter)) # 输出: 2
print(next(counter)) # 输出: 1
# print(next(counter)) # 如果取消注释,会抛出 StopIteration
print("\n使用 for 循环:")
# for 循环的本质:
# 1. 调用 iter(Countdown(3)),即 Countdown(3).__iter__(),获取迭代器对象。
# 2. 重复调用 next() on that iterator until StopIteration is raised.
for num in Countdown(3):
print(num) # 输出: 3, 2, 1

1.4 内置迭代器与 iter() 函数
许多 Python 内置类型都是可迭代对象 (Iterable),但不是迭代器本(如 list, tuple, str, dict)。你可以使用 iter() 函数从它们那里获取一个迭代器。
my_list = [1, 2, 3]
list_iterator = iter(my_list) # 获取列表的迭代器
print("迭代器")
print(next(list_iterator)) # 1
print(next(list_iterator)) # 2
# for 循环对可迭代对象做的第一件事就是调用 iter()
# 等同于:for item in iter(my_list):
print("for:")
for item in my_list:
print(item)

1.5 为什么需要迭代器?
迭代器提供了一种统一的访问接口,用于遍历各种不同的数据结构(列表、文件、数据库结果、网络流等),而无需关心其内部实现细节。这就是迭代器设计模式的核心思想。
2. 生成器 (Generator)
2.1 核心概念
生成器是 Python 提供的一种简捷、强大的工具,用于创建迭代器。它通过一种优雅的语法自动实现了迭代器协议,无需像上面那样手动定义一个类。
2.2 生成器函数
生成器函数使用 yield 语句而不是 return 来返回值。
-
当调用生成器函数时,它并不立即执行函数体,而是返回一个生成器对象(一种迭代器)。
-
每次对该生成器对象调用
next()时,函数会从上次yield语句暂停的位置继续执行,直到遇到下一个yield或函数结束。 -
yield会暂停函数并记住当前的执行状态(局部变量、指令指针等),下次调用next()时从该状态恢复。
2.3 详细解析与示例
def countdown_generator(start):
print("Generator started!")
current = start
while current > 0:
print(f"Yielding {current}")
# yield 语句产生一个值,并在此处暂停。
# 下次调用 next() 时,从下一行代码开始执行。
yield current
current -= 1
print("Generator finished!")
print("创建生成器对象:")
gen = countdown_generator(3) # 打印 Nothing yet! 函数体未执行。
print("Generator object created:", gen)
print("\n开始迭代:")
# 第一次调用 next():从函数开头执行,打印 "Started",进入循环,yield 3 并暂停。
print(next(gen)) # 输出: Generator started! \n Yielding 3 \n 3
# 第二次调用 next():从 `yield current` 的下一行 `current -= 1` 开始,循环,yield 2 并暂停。
print(next(gen)) # 输出: Yielding 2 \n 2
# 第三次调用 next():恢复,`current -= 1`,循环,yield 1 并暂停。
print(next(gen)) # 输出: Yielding 1 \n 1
# 第四次调用 next():恢复,`current -= 1` (now 0), 循环条件不满足,退出循环。
# 打印 "Finished!",函数执行完毕(隐式 return None),引发 StopIteration。
# print(next(gen)) # 会抛出 StopIteration
print("\n使用 for 循环(更常见):")
for num in countdown_generator(3):
print(f"Received in loop: {num}")
# 输出:
# Generator started!
# Yielding 3
# Received in loop: 3
# Yielding 2
# Received in loop: 2
# Yielding 1
# Received in loop: 1
# Generator finished!

2.4 生成器的优势
-
惰性求值 (Lazy Evaluation):元素只在被请求时才生成,而不是一次性在内存中创建整个序列。这对于处理大规模甚至无限的数据流至关重要(例如,读取大文件、生成斐波那契数列无限序列、监听消息队列)。
def infinite_sequence(): num = 0 while True: yield num num += 1 # 不会创建无限大的列表,只会按需生成数字 for i in infinite_sequence(): if i > 5: break print(i) # 0, 1, 2, 3, 4, 5
-
代码简洁:自动处理
__iter__,__next__,StopIteration和状态保存,逻辑清晰。 -
内存高效:同一时间只有一个元素驻留在内存中,极大节省内存空间。
2.5 生成器的方法:.send(), .throw(), .close()
生成器对象还提供了更高级的控制方法:
-
.send(value):向生成器“发送”一个值,该值会成为当前yield表达式的结果,并继续执行到下一个yield。 -
.throw(exc_type):在yield语句处向生成器抛出一个异常。 -
.close():终止生成器。
这些方法使得生成器可以用于实现协程 (Coroutines),这是更复杂的并发编程模式的基础。
3. 迭代器与生成器的关系与区别
3.1 关系
-
生成器是迭代器的一种特例。所有生成器对象都自动满足了迭代器协议(拥有
__iter__和__next__方法)。 -
你可以把生成器看作是创建迭代器的语法糖和更强大的工具。
3.2 区别
| 特性 | 迭代器 (Iterator) | 生成器 (Generator) |
|---|---|---|
| 实现方式 | 必须手动定义一个类,实现 __iter__ 和 __next__ 方法。 | 使用包含 yield 关键字的函数定义。 |
| 代码简洁性 | 相对冗长,需要显式管理状态(如 self.current)和 StopIteration。 | 极其简洁,状态由函数帧自动保存,StopIteration 自动处理。 |
| 功能强度 | 基础迭代功能。 | 除了迭代,还支持 .send(), .throw(), .close(),可用于协程。 |
| 内存使用 | 取决于实现。如果一次性加载所有数据,则内存占用大。 | 天生惰性,内存效率极高,适用于大数据流。 |
| 创建目的 | 通常用于为自定义的复杂数据结构提供迭代接口。 | 主要用于按需生成值序列,简化迭代器的创建过程。 |
| 普遍性 | 是更基础的概念。 | 是创建迭代器的首选和更常用的方式。 |
核心思想:当你需要迭代一个已存在的集合(如列表、字典)时,使用 iter() 获取它的迭代器。当你需要动态计算并生成一个序列时,几乎总是应该使用生成器。
4. 列表推导式 vs. 生成器表达式
4.1 列表推导式 (List Comprehension)
-
语法:
[expression for item in iterable if condition] -
特性: 急切实求值 (Eager Evaluation)。它会立即执行循环,构建并在内存中保存整个结果列表。
-
返回值: 一个完整的
list对象。
使用场景:
-
当你明确需要并且会多次使用整个结果列表时。
-
结果集不会太大,可以完全放入内存而不会引起内存问题。
-
你需要使用列表特有的方法,如
append(),pop(), 切片等。
示例:
# 立即计算 0 到 999999 所有数的平方,并存储在内存中
# 对于非常大的范围,这会消耗大量内存,甚至导致 MemoryError
squares_list = [x*x for x in range(1000000)]
print(squares_list[10]) # 可以立即随机访问任何元素
print(type(squares_list)) # <class 'list'>
# 文件处理(如果文件不大)
lines = [line.strip() for line in open('data.txt')]
# 可以多次遍历
for line in lines:
...
for line in lines: # 再次遍历
...
4.2 生成器表达式 (Generator Expression)
-
语法:
(expression for item in iterable if condition)(注意是圆括号()) -
特性: 惰性求值 (Lazy Evaluation)。它返回一个生成器对象,不会立即计算任何值。值只在迭代时(如
for循环或next())按需生成。 -
返回值: 一个生成器对象。
使用场景:
-
处理大规模甚至无限的数据集,无法或不应一次性加载到内存中。
-
你只需要对序列迭代一次。
-
管道式处理,将一个生成器的结果直接传递给另一个函数(如
sum(),max(),min()),这些函数可以消费迭代器。 -
结果的中间存储不重要,重要的是计算过程。
示例:
# 几乎不占用内存,只是创建了一个生成器对象
# 平方值会在 for 循环每次迭代时才被计算出来
squares_gen = (x*x for x in range(1000000))
print(squares_gen) # <generator object <genexpr> at 0x...>
print(type(squares_gen)) # <class 'generator'>
# 使用:只能迭代一次
total = sum(squares_gen) # sum() 函数会消费这个生成器
print(total)
# 此时 squares_gen 已耗尽,再迭代不会有任何输出
# for num in squares_gen:
# print(num) # Nothing
# 处理大文件的最佳实践
# 不会一次性将整个文件读入内存,而是逐行处理
line_lengths = (len(line) for line in open('huge_log_file.txt'))
print(max(line_lengths))
# 管道式处理
nums = (x for x in range(10))
squares = (x**2 for x in nums)
filtered = (x for x in squares if x % 2 == 0)
result = sum(filtered)
print(result)
4.3 关键区别总结
| 特性 | 列表推导式 | 生成器表达式 |
|---|---|---|
| 求值方式 | 急切,立即执行,构建完整列表。 | 惰性,按需生成元素。 |
| 内存占用 | 高,存储整个列表。 | 极低,同一时间只处理一个元素。 |
| 返回类型 | list | generator |
| 速度 | 创建时稍慢(要构建所有元素),但访问快(随机访问)。 | 创建极快,逐个生成元素的速度与列表推导式迭代速度相当。 |
| 迭代次数 | 可多次迭代。 | 只能迭代一次,耗尽后即为空。 |
| 语法 | 方括号 [] | 圆括号 () (在函数唯一参数时可省略) |
经验法则:默认先考虑使用生成器表达式,除非你确实需要一个完整的列表(例如,要进行切片操作或多次使用)。
5.总结
-
迭代器是提供迭代功能的协议和基础接口。
-
生成器是实现迭代器协议的强大而简洁的工具,通过
yield实现惰性求值和状态保存,是创建迭代器的首选方式。 -
列表推导式用于急切地创建列表,适用于结果集小且需多次使用的场景。
-
生成器表达式用于惰性地创建生成器,适用于处理大数据流或管道计算,极其节省内存。
更多推荐
所有评论(0)