Python 入门教程(七):迭代器与生成器
1729 字
9 分钟
Python 入门教程(七):迭代器与生成器
本篇目标
你每天都在用 for x in ...,但有没有想过 for 到底是怎么”一个个取出”元素的?这背后是 Python 的迭代协议。理解它,你就能用生成器写出处理百万行数据也不爆内存的代码——这是 Python 进阶的第一道分水岭。
一、for 循环的真相:迭代协议
可迭代对象与迭代器
nums = [1, 2, 3]
for n in nums: print(n)这段代码的幕后流程:
- Python 对
nums调用iter()拿到一个迭代器 - 反复对迭代器调用
next()取元素 - 抛出
StopIteration异常时结束循环
手动模拟一遍:
nums = [1, 2, 3]it = iter(nums) # 列表 → 迭代器
print(next(it)) # 1print(next(it)) # 2print(next(it)) # 3# print(next(it)) # ❌ StopIteration!取完了两个概念:
| 概念 | 说明 | 例子 |
|---|---|---|
| 可迭代对象 Iterable | 实现了 __iter__,能被 for 遍历 | 列表、字符串、字典、文件 |
| 迭代器 Iterator | 实现了 __iter__ 和 __next__,能被 next() | iter(列表) 的返回值 |
关系:迭代器一定可迭代;可迭代对象要通过 iter() 变成迭代器才能 next()。
from collections.abc import Iterable, Iterator
nums = [1, 2, 3]print(isinstance(nums, Iterable)) # Trueprint(isinstance(nums, Iterator)) # False!列表本身不是迭代器print(isinstance(iter(nums), Iterator)) # True迭代器是一次性的
it = iter([1, 2, 3])for x in it: print(x, end=" ") # 1 2 3for x in it: print(x, end=" ") # (空!已耗尽)而列表可以反复 for——因为每次 for 都会重新 iter() 出新迭代器。
二、生成器函数:yield 的魔法
从写一个开始
函数体里出现 yield,它就不再是普通函数,而是生成器函数——调用它不执行代码,而是返回一个生成器(一种迭代器):
def count_down(n): print("开始倒数") while n > 0: yield n # 产出 n,在此暂停! n -= 1
gen = count_down(3)print(gen) # <generator object ...> 还没执行!
print(next(gen)) # 打印"开始倒数",返回 3print(next(gen)) # 2(从暂停处继续)print(next(gen)) # 1# next(gen) # ❌ StopIterationyield 与 return 的本质区别:
return:交值、函数终结yield:交值、函数暂停,下次 next 从暂停行继续,局部变量全部保留
生成器 + for:正常使用姿势
def count_down(n): while n > 0: yield n n -= 1
for x in count_down(5): print(x, end=" ") # 5 4 3 2 1三、为什么用生成器:省内存
对比实验
生成 1000 万个平方数:
import sys
# 列表:一次性全造好放内存big_list = [x**2 for x in range(10_000_000)]print(f"列表占用: {sys.getsizeof(big_list)/1024/1024:.0f} MB") # 约 80+ MB
# 生成器:用时才算,只存"当前位置"big_gen = (x**2 for x in range(10_000_000))print(f"生成器占用: {sys.getsizeof(big_gen)} 字节") # 约 200 字节!生成器表达式:把列表推导式的 [] 换成 () 即可。数据流”边生成边消费”,内存占用恒定——处理大文件、大数据集的标准姿势。
求和场景实测
# 一亿个数求和:列表版可能内存吃紧total = sum(x for x in range(100_000_000)) # 生成器:平稳跑完print(total)四、生成器实战模式
1. 无限序列
生成器不预生成全部,所以能表达”无限”:
def fibonacci(): """无限斐波那契数列""" a, b = 0, 1 while True: # 死循环?配合 yield 就是无限流 yield a a, b = b, a + b
# 取前 10 个:islice 从无限流切片from itertools import isliceprint(list(islice(fibonacci(), 10)))# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# 找到第一个大于 1000 的斐波那契数for f in fibonacci(): if f > 1000: print(f) # 1597 break2. 读大文件(最经典场景)
def read_large_file(path): """逐行读取大文件,绝不整体载入""" with open(path, encoding="utf-8") as f: for line in f: # 文件对象本身可迭代(懒加载) yield line.strip()
# 统计一个 10GB 日志也只用几 MB 内存error_count = 0for line in read_large_file("app.log"): if "ERROR" in line: error_count += 1print(f"错误行数: {error_count}")3. 管道式处理(生成器链)
多个生成器串成流水线,数据像水流过每一站:
def lines(path): with open(path, encoding="utf-8") as f: yield from f # yield from:把子生成器的值逐个转发
def only_errors(lines_iter): for line in lines_iter: if "ERROR" in line: yield line
def with_lineno(lines_iter): for i, line in enumerate(lines_iter, 1): yield f"行{i}: {line.rstrip()}"
# 组装流水线:此刻什么都没发生(全懒)pipeline = with_lineno(only_errors(lines("app.log")))
# 消费时数据才逐条流过三层for item in pipeline: print(item)yield from iterable 等价于 for x in iterable: yield x 的简写。
4. 生成器实现滑窗
def window(seq, size): """滑动窗口:[1,2,3,4] size=2 → (1,2) (2,3) (3,4)""" for i in range(len(seq) - size + 1): yield tuple(seq[i:i+size])
print(list(window([1, 2, 3, 4, 5], 3)))# [(1, 2, 3), (2, 3, 4), (3, 4, 5)]五、itertools:迭代器工具箱(标准库)
from itertools import chain, product, combinations, islice, count
# chain:拼接多个可迭代对象print(list(chain([1, 2], "ab", (3,)))) # [1, 2, 'a', 'b', 3]
# product:笛卡尔积(嵌套循环的扁平写法)print(list(product("AB", "12")))# [('A','1'), ('A','2'), ('B','1'), ('B','2')]
# combinations:组合(不重复)print(list(combinations([1, 2, 3], 2))) # [(1,2), (1,3), (2,3)]
# count:无限计数器for i in count(10, 5): # 从10开始步进5:10,15,20... if i > 25: break print(i, end=" ") # 10 15 20 25
# islice:给无限流"切片"print(list(islice(count(1, 2), 5))) # [1, 3, 5, 7, 9]六、自定义迭代器类(预习面向对象)
给类实现 __iter__ 和 __next__,就能被 for 遍历:
class Countdown: def __init__(self, start): self.n = start
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, end=" ") # 3 2 1类的语法下一篇详讲,此处先混个脸熟。
七、何时用生成器 vs 列表
| 场景 | 选择 |
|---|---|
| 数据大(文件/百万行) | 生成器 |
| 只遍历一次 | 生成器 |
| 需反复遍历 / 下标访问 / len() | 列表 |
| 需要切片、排序完整结果 | 列表 |
| 无限/未知长度的流 | 只能生成器 |
性能口诀:一次消费用生成,多次使用存列表。
小练习
- 写生成器
running_average(nums):每接收一个数 yield 当前的平均值 - 用生成器表达式求 1~1000 中所有素数的平方和(提示:配合第五篇的 is_prime 思路)
- 写无限生成器
cycle(seq):cycle("AB")产出 A B A B…(不许看 itertools 源码) - 用管道模式实现:读入一组英文句子 → 只保留含 “python” 的行 → 全部转小写 → 前 5 条(三层生成器链)
- (选做)对比
sum([i**2 for i in range(10**7)])与sum(i**2 for i in range(10**7))的内存占用差异(用sys.getsizeof或内存监视器)
本篇小结
- for 的本质:
iter()拿迭代器 → 反复next()→ StopIteration 结束 - 可迭代 ≠ 迭代器;迭代器是一次性的
yield让函数暂停/恢复;生成器函数调用返回生成器- 生成器表达式
(x for x in ...)惰性求值,内存恒定 - 四大实战模式:无限序列、大文件、管道链、滑窗
- itertools 提供现成工具:chain/product/combinations/islice/count
下一篇:模块与标准库——站在 Python 自带的”百宝箱”上开发。
本系列为 Cyber先锋社原创教程。把”管道处理日志”例子跑通,你会对数据流有全新体感。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
Python 入门教程(七):迭代器与生成器
https://www.cyberpioneer.club/posts/python-tutorial-07/相关文章智能推荐
1
Python 入门教程(五):字典与集合
Python 教程掌握键值对容器字典的增删改查、get 与 setdefault、遍历三件套与嵌套结构,以及集合的去重、交并差运算,附词频统计与通讯录管理完整实战。
2
Python 入门教程(四):列表与元组
Python 教程深入 Python 最核心的容器:列表的增删改查全家桶、切片操作、排序与遍历技巧、浅拷贝与深拷贝的坑、元组的不可变特性与解包,附购物车与成绩统计完整案例。
3
Python 入门教程(三):条件判断与循环控制
Python 教程完整讲解 if/elif/else 分支结构、while 与 for 循环、range 与 enumerate、break/continue/else 子句、循环嵌套与推导式,用经典练习题巩固流程控制。
4
Python 入门教程(九):文件操作与异常处理
Python 教程完整讲解文件读写模式与 with 上下文管理、编码问题排查、逐行处理与追加写入,异常处理全语法(try/except/else/finally/raise)、自定义异常与工程化容错策略,附日志记录实战。
5
Python 入门教程(二):运算符与字符串详解
Python 教程详解 Python 全部七类运算符(算术/比较/赋值/逻辑/成员/身份/位运算)与字符串的索引切片、常用方法、格式化输出,配套大量可运行示例。
随机文章随机推荐



