Python 中的可迭代、迭代器与生成器——从协议到实现再到最佳实践

目录

1.引言:为什么它们重要?

2.可迭代(Iterable):协议与检测

3.迭代器(Iterator):状态、惰性、一次性

4.生成器(Generator):用暂停代替回调

5.三者的关系与区别:一张思维导图

6.进阶:自定义可迭代对象与生成器协程

7.性能、陷阱与调试技巧

8.总结与展望


1.引言:为什么它们重要?

在 Python 中,几乎所有"能放在 for...in... 里循环"的东西背后都遵循同一套协议:Iterable → Iterator → StopIteration。掌握这套体系,不仅能写出更 Pythonic 的代码,还能带来三大收益:

• 内存友好:按需生成值,避免一次性加载大数据;

• 代码解耦:生产者与消费者通过迭代协议解耦;

• 并发友好:生成器让"异步回调地狱"变成"顺序协程"。

2.可迭代(Iterable):协议与检测

2.1 什么是可迭代

官方定义:实现了 __iter__() 方法并返回一个迭代器对象,或者实现了序列协议(__getitem__ 从 0 开始且无越界抛 IndexError)。

2.2 检测手段

python 复制代码
from collections.abc import Iterable, Iterator
isinstance([1, 2, 3], Iterable)   # True
isinstance('abc', Iterable)       # True
isinstance(123, Iterable)         # False

2.3 常见误区

• 可迭代 ≠ 迭代器。列表是可迭代,但不是迭代器。

• 文件对象 open() 返回的 file 既是可迭代,也是迭代器;但 list(file) 会把文件指针移到末尾,再次迭代为空。

2.4 自定义可迭代类

python 复制代码
class Countdown:
    def __init__(self, start):
        self.start = start
    def __iter__(self):
        n = self.start
        while n > 0:
            yield n
            n -= 1

要点:

__iter__ 可以返回一个新的迭代器,也可以直接写成生成器函数(如上)。

• 推荐返回新的迭代器实例,而不是 self,否则多次迭代会共享状态。

3.迭代器(Iterator):状态、惰性、一次性

3.1 迭代器协议

必须同时实现:

__iter__() ------ 返回自身;

__next__() ------ 返回下一个值,耗尽时抛 StopIteration

3.2 手动迭代示范

python 复制代码
it = iter([10, 20, 30])
next(it)  # 10
next(it)  # 20
next(it)  # 30
next(it)  # StopIteration

3.3 状态机视角

迭代器是一个有状态的游标,只能前进不能后退,且遍历完即"报废"。

3.4 典型实现

python 复制代码
class Squares:
    def __init__(self, n):
        self.i, self.n = 0, n
    def __iter__(self):
        return self
    def __next__(self):
        if self.i >= self.n:
            raise StopIteration
        self.i += 1
        return (self.i - 1) ** 2

4.生成器(Generator):用暂停代替回调

4.1 生成器函数

使用 yield 关键字即可把任何普通函数变成生成器工厂:

python 复制代码
def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

调用 fib() 并不执行函数体,而是返回一个 generator 对象。

4.2 生成器表达式
(expr for x in iterable if cond),语法糖,节省临时列表:

python 复制代码
squares = (x*x for x in range(1000000))

4.3 send / throw / close ------ 双向通信

python 复制代码
def echo():
    received = yield 'READY'
    while True:
        received = yield f'ECHO:{received}'
        
gen = echo()
next(gen)       # 启动到第一个 yield,返回 'READY'
gen.send('hi')  # 发送并获取 'ECHO:hi'
gen.throw(ValueError, 'boom')  # 抛异常到生成器内部
gen.close()     # 触发 GeneratorExit

4.4 yield from ------ 委托子生成器

python 复制代码
def chain(*iterables):
    for it in iterables:
        yield from it

等价于:

python 复制代码
for it in iterables:
    for x in it:
        yield x

yield from 会自动处理子生成器的 send/throw/return,适用于协程调度器。

4.5 生成器与协程

PEP 342 以后,生成器变成"可暂停的协程",为 async/await 铺路。

5.三者的关系与区别:一张思维导图

可迭代:只要"能被迭代",不保证惰性。

├── 序列型:list、str、tuple ------ 内存全载。

└── 生成器型:generator、range ------ 惰性。

• 迭代器:可迭代的一种,额外实现 __next__,状态化。

├── 由 iter(obj) 获得。

└── 生成器对象自身就是迭代器。

• 生成器:一种特殊的迭代器,通过编译器自动实现 __iter__/__next__,支持挂起/恢复栈帧。

6.进阶:自定义可迭代对象与生成器协程

6.1 可迭代对象的多遍遍历

python 复制代码
class LineReader:
    def __init__(self, path):
        self.path = path
    def __iter__(self):
        with open(self.path) as f:
            for line in f:
                yield line.rstrip('\n')

每次 for 循环都会新建文件句柄,保证可重入。

6.2 迭代器的惰性切片
itertools.islice(iterable, start, stop, step) 可以在不展开序列的情况下切片。

6.3 协程式任务调度器(简化版)

python 复制代码
import heapq, time
def sleep(delay):
    end = time.time() + delay
    while time.time() < end:
        yield

class Scheduler:
    def __init__(self):
        self.ready = []
    def call_later(self, delay, func):
        heapq.heappush(self.ready, (time.time()+delay, func))
    def run(self):
        while self.ready:
            when, func = heapq.heappop(self.ready)
            delay = max(0, when - time.time())
            time.sleep(delay)
            for _ in func():
                pass  # 驱动生成器

该调度器用生成器模拟了"非阻塞 sleep",是 asyncio 的极简原型。

7.性能、陷阱与调试技巧

7.1 性能对比

• 列表推导 vs 生成器表达式:
[f(x) for x in data] 立即占用 O(n) 内存;
(f(x) for x in data) 占用 O(1)。

• 运行速度:生成器每次 yield 都有函数调用开销,极端情况下比列表慢 10%--30%,但通常被 I/O 掩盖。

7.2 陷阱

• 迭代器只能遍历一次:

python 复制代码
it = iter([1,2,3])
list(it)   # [1,2,3]
list(it)   # []

• 生成器 throw/close 不会自动清理外部资源,需配合 try/finally 或 contextlib.closing

• 在生成器里捕获 GeneratorExit 后禁止再 yield,否则抛 RuntimeError

7.3 调试技巧

• 使用 inspect.getgeneratorstate(gen) 查看挂起状态。

• 在生成器内部 yield 前后打印日志,或借助 yield (debug_info, real_value) 元组。

pdb 支持单步调试生成器,命令 n 会跨 yield;用 s 进入子生成器。

8.总结与展望

可迭代、迭代器与生成器构成了 Python 数据管道的核心抽象:

• 可迭代回答"能不能 for";

• 迭代器回答"如何一个一个拿";

• 生成器用最小的语法成本,让"惰性 + 状态 + 双向通信"成为日常。

随着 Python 3 引入 async forasync defanext(),生成器协议进一步演进为"异步迭代协议"。未来,无论是大数据流处理(pandas 2.0 的 Arrow 后端)、HTTP/3 的流式响应,还是 WebAssembly 驱动的浏览器 Python,迭代器思想都将继续发光。

相关推荐
Metaphor6926 小时前
使用 Python 在 Word 文档中添加批注
python·word
FreakStudio6 小时前
WIZnet 开源方案分享:W55MH32 开发板 + 小智 AI实现以太网版本的小智机器人
python·单片机·嵌入式·大学生·面向对象·并行计算·电子diy·电子计算机
栈溢出了6 小时前
Java Lambda 表达式笔记
java·开发语言·intellij-idea
会周易的程序员6 小时前
使用 pybind11 封装 C++ 日志库 microLog 为 Python 模块
java·c++·python·物联网·架构·日志·aiot
zhixingheyi_tian6 小时前
一份GC日志的解读
java·开发语言
czhaii6 小时前
串口1中断收发-C语言-MODBUS协议
c语言·开发语言
一只专注api接口开发的技术猿7 小时前
电商评论自动化监控与情感数据分析完整落地教程(附可直接运行 Python 代码)
大数据·数据库·python·数据分析·自动化
Fox爱分享7 小时前
字节二面:1000瓶酒,有一瓶是毒药,多少只老鼠可以查出来?
算法·面试·程序员
HAPPY酷7 小时前
【ROS2】 Conda 环境搭建(命令行版)
linux·运维·python·ubuntu·机器人·conda
+wacyltd大模型备案算法备案7 小时前
大模型评估测试题库怎么建?风险分类、测试样本的完整方法
人工智能·算法·安全·分类·大模型·大模型备案·大模型上线登记