/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}
1. 引言
在处理大数据、读取超大文件或构造无限序列时,一次性把所有数据加载进内存往往不可行,甚至直接导致程序崩溃。Python 的迭代器(Iterator)与生成器(Generator)正是为解决这类问题而生:它们采用惰性计算(Lazy Evaluation),按需逐个产出元素,从而大幅降低内存占用。
本文将从可迭代对象与迭代器协议讲起,深入剖析 iter() / next() 的运作机制,再介绍 yield 生成器与生成器表达式,最后通过大文件读取与无限序列两个实战案例,展示惰性计算如何优化内存。
2. 可迭代对象与迭代器协议
2.1 什么是可迭代对象
可迭代对象(Iterable) 是实现了 __iter__() 方法的对象,可以被 for 循环遍历。常见的可迭代对象包括:
- list、tuple、str
- dict、set
- 文件对象
- 自定义类(实现了 __iter__())
lst = [1, 2, 3]
print(hasattr(lst, "__iter__")) # True,list 是可迭代对象
2.2 迭代器协议
迭代器(Iterator) 是实现了迭代器协议的对象,需要同时具备两个方法:
- __iter__():返回迭代器自身。
- __next__():返回下一个元素;没有更多元素时抛出 StopIteration 异常。
class CountDown:
def __init__(self, n):
self.n = n
def __iter__(self):
return self
def __next__(self):
if self.n <= 0:
raise StopIteration
self.n -= 1
return self.n + 1
for x in CountDown(3):
print(x) # 依次输出 3、2、1
2.3 可迭代对象 vs 迭代器
| 可迭代对象 | 是 | 否,可重复遍历 | list、str |
| 迭代器 | 是 | 是,遍历后耗尽 | iter(list)、生成器 |
lst = [1, 2, 3]
it = iter(lst) # 把可迭代对象转成迭代器
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# print(next(it)) # 报错:StopIteration
3. iter 与 next 详解
3.1 iter() 函数
iter() 用于把可迭代对象转换为迭代器:
s = "hello"
it = iter(s)
print(next(it)) # h
print(next(it)) # e
iter() 也可以配合哨兵值使用,用于反复调用某个函数直到返回哨兵值:
def read_line():
return input("输入(输入 end 结束):")
for line in iter(read_line, "end"):
print("收到:", line)
3.2 next() 函数
next() 用于获取迭代器的下一个元素,可传入默认值避免 StopIteration:
it = iter([1, 2])
print(next(it, "结束")) # 1
print(next(it, "结束")) # 2
print(next(it, "结束")) # 结束,不抛异常
3.3 for 循环的本质
for 循环本质上就是 iter() + next() 的语法糖:
it = iter([1, 2, 3])
while True:
try:
x = next(it)
print(x)
except StopIteration:
break
4. yield 生成器
4.1 什么是生成器
生成器(Generator) 是包含 yield 关键字的函数。调用生成器函数不会立即执行函数体,而是返回一个生成器对象(迭代器)。每次调用 next() 时,函数执行到 yield 处暂停,并产出值;下次调用时从暂停处继续。
def count_up_to(n):
i = 1
while i <= n:
yield i
i += 1
gen = count_up_to(3)
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
4.2 惰性计算的威力
生成器最大的价值在于惰性计算:元素按需生成,不一次性全部创建。
def squares(n):
for i in range(n):
yield i * i
# 生成器逐个产出,内存占用恒定
for s in squares(1000000):
if s > 100:
break
print(s) # 只计算到 100 就停止
4.3 yield 与 return 的区别
- yield:暂停函数,产出值,可多次使用。
- return:结束函数,返回最终结果。
def gen_demo():
yield 1
yield 2
return "done" # 结束,返回值不会作为元素产出
g = gen_demo()
print(next(g)) # 1
print(next(g)) # 2
# print(next(g)) # 报错:StopIteration
5. 生成器表达式
5.1 语法
生成器表达式与列表推导式语法相似,但使用圆括号,且惰性求值:
# 列表推导式:一次性生成全部元素
squares_list = [x * x for x in range(10)]
# 生成器表达式:惰性生成
squares_gen = (x * x for x in range(10))
print(type(squares_list)) # <class 'list'>
print(type(squares_gen)) # <class 'generator'>
5.2 内存对比
import sys
list_comp = [x for x in range(1000000)]
gen_exp = (x for x in range(1000000))
print(sys.getsizeof(list_comp)) # 约 8000056 字节
print(sys.getsizeof(gen_exp)) # 约 112 字节,内存占用极小
5.3 使用场景
生成器表达式适合只需遍历一次的场景,例如求和、取最大值:
total = sum(x * x for x in range(1000)) # 无需中间列表
print(total)
6. 实战案例
6.1 大文件读取
一次性读取大文件会占用大量内存,甚至导致内存溢出。使用生成器逐行读取,内存占用恒定:
def read_large_file(file_path):
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
yield line.strip()
# 逐行处理,内存占用与文件大小无关
for line in read_large_file("huge_log.txt"):
if "ERROR" in line:
print(line)
6.2 无限序列
生成器可以轻松构造无限序列,配合 itertools.islice 按需取用:
import itertools
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# 取前 10 个斐波那契数
fib = fibonacci()
print(list(itertools.islice(fib, 10)))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
6.3 流式处理管道
生成器可以串联成处理管道,每个环节只处理一个元素,内存占用始终恒定:
def read_lines(file_path):
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
yield line
def filter_keyword(lines, keyword):
for line in lines:
if keyword in line:
yield line
def count_words(lines):
for line in lines:
yield len(line.split())
# 管道:读取 → 过滤 → 统计,全程惰性
lines = read_lines("data.txt")
filtered = filter_keyword(lines, "Python")
word_counts = count_words(filtered)
for count in word_counts:
print(count)
7. 总结
理解迭代器与生成器,核心要点如下:
- 可迭代对象实现 __iter__(),可被 for 遍历;迭代器额外实现 __next__(),遍历后耗尽。
- iter() 把可迭代对象转为迭代器,next() 逐个取元素,耗尽时抛 StopIteration。
- yield 生成器采用惰性计算,按需产出元素,内存占用恒定。
- 生成器表达式比列表推导式更省内存,适合只需遍历一次的场景。
- 大文件读取、无限序列、流式管道是生成器的典型应用。
掌握惰性计算的思维,能让你在处理海量数据时写出更高效、更省内存的 Python 代码。
![打卡信奥刷题(3676)用C++实现信奥题 P12271 [蓝桥杯 2024 国 Python B] 括号与字母-171主机测评](https://www.171host.com/wp-content/uploads/2026/11/20261107050125-6aeeb0a5a5ade-220x150.png)
