python11(可迭代对象 & 迭代器 & 生成器)
可迭代对象、迭代器与生成器 —— 迭代协议的系统性解析
在基于昨日的基础上,我们今天继续学习可迭代对象 & 迭代器 & 生成器这部分内容;Python 的 for 循环机制虽直观易用,但其底层运作原理常被忽略。为何列表、字符串等类型支持循环,而数值类型不能?为何某些对象只能被遍历一次?这些现象均指向 迭代协议(Iteration Protocol)这一核心概念。本文旨在系统梳理可迭代对象(Iterable)、迭代器(Iterator)与生成器(Generator)之间的层次关系与实现细节。
一、基本概念界定
- 可迭代对象(Iterable):指能够被
for语句遍历的对象,如列表、字符串、字典、集合及文件对象等。 - 迭代器(Iterator):在可迭代对象的基础上,额外具备状态记录能力,能够记住当前遍历位置,并逐一返回后续元素。迭代器自身亦是可迭代对象。
- 生成器(Generator):一种特殊的迭代器,通过
yield关键字定义,其书写形式类似函数,而执行行为符合迭代器协议。
层次关系:
生成器 ⊂ 迭代器 ⊂ 可迭代对象
即:所有生成器均为迭代器,所有迭代器均为可迭代对象,但逆命题不成立。
二、类型判定方法
借助 collections.abc 模块(或旧版 collections)中的 Iterable、Iterator、Generator 抽象基类,可通过 isinstance() 函数进行精确类型检测。
from collections.abc import Iterable, Iterator, Generator
# 常见内置类型检测示例
num = 123
s = "abc"
lst = [1,2,3]
tup = (1,2,3)
d = {"a":1, "b":2}
st = {1,2,3}
r = range(5)
print(isinstance(num, Iterable)) # False(数值类型不支持)
print(isinstance(s, Iterable)) # True
print(isinstance(lst, Iterable)) # True
print(isinstance(r, Iterable)) # True(range 对象为可迭代对象)
# 上述类型均非迭代器
print(isinstance(lst, Iterator)) # False
print(isinstance(r, Iterator)) # False
特殊内置对象:reversed()、zip()、enumerate() 等函数的返回值均为迭代器。
rev = reversed([1,2,3])
zip_obj = zip([1,2], ['a','b'])
enum_obj = enumerate([10,20])
print(isinstance(rev, Iterator)) # True
print(isinstance(zip_obj, Iterator)) # True
print(isinstance(enum_obj, Iterator)) # True
注意:
isinstance(obj, Iterable)主要检测对象是否实现__iter__()方法。但某些对象仅通过__getitem__()方法亦可被for遍历(例如自定义序列类),此时isinstance可能返回False,而for循环仍能正常工作。后文将就此展开讨论。
三、可迭代对象 —— 被 for 遍历的充要条件
一个对象若满足以下任一条件,即为可迭代对象:
- 实现迭代协议:拥有
__iter__()方法,且该方法返回一个迭代器。 - 实现序列协议:拥有
__getitem__()方法,且接受从0开始的整数索引并顺序返回元素(适用于旧式序列)。
Python 内置的字符串、列表、元组、字典、集合及 range 对象均实现 __iter__(),因此均为可迭代对象。而数值类型未实现上述任一协议,故不可迭代。
自定义可迭代对象的两种实现方式
方式一:实现 __iter__()(标准推荐)
class MyIterable:
def __init__(self, data):
self.data = data
def __iter__(self):
# 返回一个迭代器实例(通常为自身或专门的迭代器类对象)
return MyIterator(self.data)
方式二:仅实现 __getitem__()(备选方案)
class MySequence:
def __init__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index]
此时,for item in MySequence([5,6,7]): 仍可正常执行。原因在于 for 循环内部会优先尝试调用 __iter__(),若不存在,则转而调用 __getitem__(),从索引 0 开始依次取值,直至抛出 IndexError 终止。
注意:
isinstance(obj, Iterable)仅识别__iter__(),不认可__getitem__(),因此该检测方式对仅实现序列协议的对象存在局限性。
四、迭代器 —— 具备状态保持能力的迭代载体
迭代器在可迭代对象的基础上,额外实现了 __next__() 方法,用于逐一返回下一个元素;当无更多元素时,抛出 StopIteration 异常以终止迭代。
迭代器的核心方法
__iter__():返回迭代器自身(迭代器亦为可迭代对象,满足for循环的初始调用要求)。__next__():返回下一个有效元素,若无则抛出StopIteration。
自定义迭代器示例(经典模式)
class ContainerIterator:
def __init__(self, container):
self.container = container
self.cursor = 0
def __iter__(self):
return self
def __next__(self):
if self.cursor >= len(self.container.data):
raise StopIteration
item = self.container.data[self.cursor]
self.cursor += 1
return item
class Container:
def __init__(self, data):
self.data = data
def __iter__(self):
return ContainerIterator(self)
上述代码允许 for i in Container([4,5,6]): print(i) 正常遍历。其底层执行流程为:
for语句调用Container.__iter__()获取迭代器对象。- 每轮循环调用该迭代器的
__next__()方法。 - 直至
StopIteration被捕获,循环终止。
手动模拟 for 循环的实现机制
cont = Container([4,5,6])
it = cont.__iter__()
while True:
try:
item = it.__next__()
print(item)
except StopIteration:
break
五、生成器 —— 通过 yield 实现惰性求值的迭代器
生成器是构建迭代器最为简洁的方式。开发者无需显式定义 __iter__ 与 __next__,只需在函数体内使用 yield 关键字返回值,Python 解释器将自动将其编译为生成器对象。
普通函数与生成器函数的对比
# 普通函数:return 终止执行
def normal():
print("start")
return 2
print("end") # 不可达代码
# 生成器函数:yield 挂起执行
def gen():
print("start")
yield 2
print("continue")
yield 4
print("end")
调用 gen() 不会立即执行函数体,而是返回一个生成器对象,该对象是迭代器的子类。
g = gen()
print(isinstance(g, Generator)) # True
print(isinstance(g, Iterator)) # True
执行流程:每次 __next__() 从挂起点恢复
print(g.__next__()) # 输出 "start",返回 2,并挂起
print(g.__next__()) # 输出 "continue",返回 4,并挂起
# 再次调用将抛出 StopIteration,同时输出 "end"(函数体执行完毕)
关键特性:
yield不仅返回一个值,还会保存当前函数的所有局部变量状态,下次调用时从该状态恢复执行。此特性使其在处理需维护状态的遍历场景中具有显著优势。
生成器的遍历方式
for val in gen():
print(val) # 输出 2, 4
需要强调的是,同一生成器对象仅能遍历一次,因其内部状态单向推进,无法重置。
六、生成器表达式 —— 内存优化的“惰性”推导式
列表推导式 [i for i in range(5)] 会立即构建完整列表,占用与数据量成正比的内存空间。若数据规模巨大,可采用生成器表达式,将方括号替换为圆括号,从而实现惰性求值。
# 列表推导式 — 立即构建
list1 = [i for i in range(5)]
print(sum(list1)) # 10
print(sum(list1)) # 10(列表可重复使用)
# 生成器表达式 — 惰性求值
gen_exp = (i for i in range(5))
print(sum(gen_exp)) # 10(迭代完成后耗尽)
print(sum(gen_exp)) # 0(因已为空)
生成器表达式尤其适用于一次性消费的大数据场景,例如 sum(i for i in range(10**9)),其内存占用几乎恒定。
提示:若生成器表达式作为函数的唯一参数,外层括号可省略,如
sum(i for i in range(5)),使代码更为简洁。
七、内置工具函数 —— iter() 与 next() 的用法
iter(object[, sentinel]) —— 获取迭代器
- 单参数形式:对象必须实现
__iter__()或__getitem__(),否则引发TypeError。 - 双参数形式:第一个参数须为可调用对象(如函数),第二个为哨兵值。每次调用该可调用对象,直至返回值等于哨兵时停止迭代。
# 常规用法
it = iter([1,2,3])
# 哨兵用法示例:读取文件直至空行
with open('file.txt') as f:
for line in iter(f.readline, ''):
process(line)
next(iterator[, default]) —— 获取下一元素
- 若迭代器未耗尽,返回下一个元素;若已耗尽且提供了
default,则返回该默认值;否则抛出StopIteration。
it = iter("abc")
print(next(it)) # 'a'
print(next(it)) # 'b'
print(next(it, "end")) # 'c'(未耗尽,返回实际值)
print(next(it, "end")) # 'end'(耗尽,返回默认值)
八、迭代器的优势与局限性
| 优势 | 局限 |
|---|---|
| 统一遍历接口,无需依赖索引 | 不支持随机访问(无法直接获取中间元素) |
| 内存高效,每次仅处理一个元素,适合大规模或流式数据 | 单向遍历,不可回退 |
| 支持惰性计算,按需生成 | 未知长度(无 len()),除非完全遍历 |
可被 for、list()、sum() 等统一消费 | 一次性使用,耗尽后需重新创建 |
实践建议:
- 处理大文件、数据流或无限序列时,优先选用生成器或迭代器。
- 若需重复使用或频繁随机访问,则使用列表、元组等容器类型。
九、三者关系总结(层次结构图)
可迭代对象 (Iterable)
├── 条件:实现 __iter__() 或 __getitem__()
├── 实例:list, str, dict, range, ...
│
├── 迭代器 (Iterator)
│ ├── 条件:实现 __iter__() + __next__()
│ ├── 实例:reversed, zip, enumerate, 自定义迭代器类
│ └── 性质:迭代器自身也是可迭代对象(__iter__ 返回自身)
│
└── 生成器 (Generator)
├── 创建方式:yield 函数 或 生成器表达式
├── 性质:生成器是迭代器的子类型,因此也是可迭代对象
└── 特征:惰性求值、可挂起、内存占用低
结论:
所有可被
for遍历的对象均为可迭代对象;迭代器是具备内部遍历指针的可迭代对象;生成器是能够显式挂起和恢复执行的迭代器,兼具函数式表述与迭代器特性,是处理惰性序列的首选工具。
验证性练习(示例)
def foo():
for i in range(3):
yield i
g = foo()
print(list(g)) # ①
print(list(g)) # ②
print(sum(g)) # ③
# **输出结果**:
# ① `[0,1,2]`
# ② `[]`
# ③ `0`
解释:生成器对象 g 在第一次 list(g) 时已耗尽,后续调用返回空列表,sum 作用于空迭代器结果为 0。这验证了生成器的单向一次性消费特性。
以上为迭代协议体系的完整论述。掌握这些概念,有助于在数据处理、流式计算及资源敏感型应用中做出合理的技术选型。好了今天的内容就到这儿了如有疑问,欢迎进一步探讨。
下图为本文知识点的思维导图(示意):

更多推荐



所有评论(0)