注:学习资料来源<尚硅谷>
视频:点击跳转
代码仓库:pythonStudy
在 Python 的学习和工程实践中,迭代器(iterator) 与 生成器(generator) 是两个绕不开的重要主题。很多开发者初学时能够机械地使用 for 循环,却并不真正理解:为什么列表、字符串可以被遍历?为什么 next() 能逐个取值?为什么 yield 能让函数“暂停”?这些现象背后,其实都指向 Python 的一套统一抽象——迭代协议。
本文将围绕“可迭代对象、迭代器、生成器”三个层次展开,系统说明它们的定义、联系、底层工作方式、典型实现以及应用场景。文章尽量以自然段形式组织内容,同时兼顾术语准确性与可读性,帮助你从“会用”走向“理解原理”。
一、为什么要理解迭代机制
在 Python 中,遍历数据是极其高频的操作。无论是处理列表、读取文件、遍历数据库游标,还是消费网络数据流,本质上都离不开“按顺序一个个拿数据”这件事。Python 为这类顺序访问行为设计了非常统一的协议,开发者日常所写的 for item in obj: 只是其表层语法糖。
理解迭代器与生成器,不只是为了应对语法题,更重要的是它会直接影响你对以下问题的认知:
第一,如何设计一个对象,使它能够被 for 循环自然地遍历。第二,如何以更低的内存成本处理大规模数据。第三,如何实现延迟计算、流式处理、按需生成结果。第四,如何读懂一些框架、标准库和第三方库中的高级代码结构。
可以说,迭代器是 Python 数据访问模型的重要组成部分,而生成器则是 Python 对迭代器的一种语法级增强。
二、先建立基础:什么是可迭代对象
在正式讨论迭代器之前,必须先区分一个经常被混淆的概念:可迭代对象(iterable)。
所谓可迭代对象,最直观的理解就是:能够被 for 循环遍历的对象。例如列表、元组、字符串、集合、字典、range 对象,甚至很多自定义类型,都属于可迭代对象。只要某个对象可以出现在 for x in obj 这一语法结构中,它通常就具备可迭代性。
例如:
names = ['张三', '李四', '王五']
citys = ('北京', '上海', '深圳')
msg = 'hello'
for item in names:
print(item)
for item in citys:
print(item)
for item in msg:
print(item)这些对象都可以自然地进入 for 循环,因此它们是可迭代对象。
与之相对,普通整数、浮点数、布尔值这类标量对象,默认并不具备可迭代性。普通函数对象通常也不能直接被 for 遍历。例如:
age = 10
def test():
pass如果你尝试对 age 或 test 进行 for 遍历,程序会报错。根本原因在于,它们没有遵循 Python 所要求的迭代接口。
从实现角度看,一个对象之所以“通常”能被迭代,是因为它提供了 __iter__ 方法。这个方法是 Python 数据模型中的一个特殊方法,也常被称为“魔法方法”或“双下划线方法”。你可以用 hasattr(obj, '__iter__') 粗略判断一个对象是否具备迭代能力。例如:
names = ['张三', '李四', '王五']
citys = ('北京', '上海', '深圳')
msg = 'hello'
age = 10
print(hasattr(names, '__iter__'))
print(hasattr(citys, '__iter__'))
print(hasattr(msg, '__iter__'))
print(hasattr(age, '__iter__'))需要注意的是,从概念上说,“可迭代对象”强调的是它可以提供一个迭代器,而不是说它自身就一定是迭代器。这一点非常关键,也是很多初学者容易混淆的地方。
三、迭代器是什么:真正负责逐个取值的对象
如果说可迭代对象描述的是“可以被遍历”,那么迭代器描述的则是“如何一步一步取出元素”。
在 Python 中,调用一个可迭代对象的 __iter__() 方法,或者直接使用内置函数 iter(obj),通常会得到一个迭代器对象(iterator)。例如:
names = ['张三', '李四', '王五']
print(names.__iter__())
print(iter(names))这里得到的并不是原列表本身,而是一个专门用于遍历该列表的迭代器。换句话说,可迭代对象负责“提供数据源”,迭代器负责“维护遍历状态并逐项产出数据”。
迭代器的核心能力体现在 __next__() 方法上。每次调用 next(it),本质上就是在调用 it.__next__()。它会根据迭代器当前保存的状态,返回下一个元素。例如:
names = ['张三', '李四', '王五']
it = iter(names)
print(next(it))
print(next(it))
print(next(it))程序会依次输出三个元素。当所有元素都被取完之后,如果继续调用 next(it),解释器就会抛出 StopIteration 异常。这不是错误设计,而是 Python 迭代协议的一部分:通过 StopIteration 告诉外部“数据已经取完了”。
try:
print(next(it))
except StopIteration:
print('迭代结束')因此,从协议层面看,迭代器最重要的语义就是:它是一个能记住当前位置、并能不断向前推进的数据访问对象。
四、for 循环到底做了什么
很多人把 for 循环理解成一种“遍历语法”,但如果只停留在这个层面,就很难真正理解 Python 的对象模型。实际上,for 循环并不是为列表、元组、字符串分别设计的一套机制,它依赖的是一套统一的迭代协议。
我们平时这样写代码:
names = ['张三', '李四', '王五']
for item in names:
print(item)其背后的逻辑大致可以理解为:
先对
names调用iter(names),获取一个迭代器对象。然后反复调用
next(it)获取下一个值。一旦捕获到
StopIteration异常,就停止循环。
这套逻辑可以手动改写为:
names = ['张三', '李四', '王五']
it = iter(names)
while True:
try:
item = next(it)
print(item)
except StopIteration:
break这段代码的意义非常大。它揭示了一个事实:for 循环的本质不是“遍历某种容器”,而是“消费一个迭代器”。只要一个对象能够按照迭代协议提供迭代器,那么它就可以无缝接入 for 循环。
五、迭代器协议:什么样的对象才算迭代器
Python 对迭代器有一套比较明确的要求,通常称为迭代器协议(iterator protocol)。
一个对象如果想成为迭代器,一般需要满足两个条件。
第一,它必须能够被 iter() 接受。也就是说,调用 iter(obj) 时应该返回一个合法结果。对于真正的迭代器来说,iter(obj) 通常返回它自己。
第二,它必须支持 next() 调用。也就是说,调用 next(obj) 时,能够按顺序不断返回下一个元素;当元素耗尽时,应抛出 StopIteration。
因此,典型的迭代器通常会同时实现:
__iter__()__next__()
其中,__iter__() 返回自身,__next__() 负责按状态推进并返回值。
例如,内置列表迭代器就符合这个特征:
names = ['张三', '李四', '王五']
it = iter(names)
print(iter(it) is it) # True这意味着:迭代器既是“可迭代的”,又是“可逐项取值的”。之所以这样设计,是为了保证 for 循环在面对迭代器时也能继续正常工作。
六、迭代器的一个重要特性:一次性、可消耗
迭代器和列表最大的区别之一,在于它通常是状态性的、一次性的、可消耗的。
列表更像一个稳定的数据容器,你可以多次遍历它,每次 for 循环都会重新开始。而迭代器更像一个带游标的数据访问器,它记录着当前已经遍历到哪里。一旦元素被取走,状态就向前推进,不会自动回退。
来看一个例子:
names = ['张三', '李四', '王五']
it1 = iter(names)
it2 = iter(names)
print(next(it1))
print(next(it1))
print(next(it1))
try:
print(next(it1))
except StopIteration:
print('it1 已经耗尽')
print(next(it2))
print(next(it2))
print(next(it2))这说明 it1 和 it2 虽然都来自同一个列表,但它们是两个独立的迭代器实例,各自维护自己的遍历状态。it1 被消耗完以后,不能自动恢复;如果想重新遍历,就要重新创建新的迭代器。
这也是为什么在一些工程场景里,开发者会强调:不要轻易重复消费同一个生成器或迭代器对象,因为它们可能早已在前一次处理中被耗尽。
七、自定义迭代器:让自己的对象也支持 for
理解迭代协议之后,我们就可以让自定义对象也具备“可遍历能力”。这在框架设计、业务模型抽象、数据结构封装中非常实用。
1. 方案一:可迭代对象与迭代器分离
这是更符合面向对象职责分离思想的一种设计方式。对象本身负责保存业务数据,而专门的迭代器类负责维护遍历状态。
class Person:
def __init__(self, name, age, gender, address):
self.name = name
self.age = age
self.gender = gender
self.address = address
def __iter__(self):
return PersonIterator(self)
class PersonIterator:
def __init__(self, p):
self.p = p
self.index = 0
self.attrs = [p.name, p.age, p.gender, p.address]
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.attrs):
raise StopIteration
value = self.attrs[self.index]
self.index += 1
return value现在,Person 的实例就可以被 for 循环遍历:
p1 = Person('张三', 18, '男', '北京昌平')
for item in p1:
print(item)这种写法的优点在于结构清晰。Person 本身只是“数据提供者”,而 PersonIterator 是“遍历执行者”。在很多复杂系统中,这种职责分离有利于代码维护与扩展。
2. 方案二:对象本身既是可迭代对象,又是迭代器
另一种常见方式,是让同一个对象同时实现 __iter__() 和 __next__()。这样可以减少类的数量,写法更紧凑。
class Person:
def __init__(self, name, age, gender, address):
self.name = name
self.age = age
self.gender = gender
self.address = address
self.__index = 0
self.__attrs = [name, age, gender, address]
def __iter__(self):
self.__index = 0
return self
def __next__(self):
if self.__index >= len(self.__attrs):
raise StopIteration
value = self.__attrs[self.__index]
self.__index += 1
return value这里需要特别注意一点:由于对象本身兼任迭代器,它内部保存了遍历状态,所以在 __iter__() 中最好重置索引,否则重复遍历时可能产生意料之外的结果。
这种写法虽然简洁,但在语义上把“数据对象”和“遍历器”合并到了一起。对于简单场景没有问题,但复杂业务下仍建议根据职责边界谨慎使用。
八、迭代器真正的核心:__next__() 决定一切
如果说 __iter__() 是“入口”,那么 __next__() 才是迭代器的核心所在。因为迭代器真正“玩”的其实就是状态推进与按需返回。
你完全可以在 __next__() 中不只是返回原始数据,而是在返回之前对数据进行格式化、转换、过滤甚至懒计算。例如:
from cn2an import an2cn
class Person:
def __init__(self, name, age, gender, address):
self.name = name
self.age = age
self.gender = gender
self.address = address
self.__index = 0
self.__attrs = [name, age, gender, address]
def __iter__(self):
self.__index = 0
return self
def __next__(self):
if self.__index >= len(self.__attrs):
raise StopIteration
value = self.__attrs[self.__index]
if isinstance(value, str):
value = value.upper()
if isinstance(value, int):
value = an2cn(value)
self.__index += 1
return value这样,遍历 Person 对象时,字符串会自动转换为大写,整数会被转换为中文数字。这个例子很好地说明:迭代器并不只是“挨个取值”,它还可以承担访问控制、数据转换、惰性加工等职责。
因此,在更高阶的设计中,迭代器往往不仅是容器的附属品,还可能是一种“数据流处理接口”。
九、迭代器的工程价值:惰性计算与内存友好
迭代器最重要的优势之一,是它天然支持惰性计算(lazy evaluation)。
所谓惰性计算,就是不在一开始就把所有结果全部算出来、全部存起来,而是在真正需要某个结果时,再生成那个结果。也就是说,它遵循的是“按需生产”的策略,而不是“一次性构造”的策略。
这一特性在面对大规模数据时价值极高。因为如果你一次性构建所有结果,就意味着需要预先占用足够大的内存;而如果采用迭代器,每次只生成一个结果,内存压力就会小得多。
以斐波那契数列为例,可以用迭代器实现:
class Fibo:
def __init__(self, total):
self.total = total
self.index = 0
self.pre = 1
self.cur = 1
def __iter__(self):
return self
def __next__(self):
if self.index >= self.total:
raise StopIteration
if self.index < 2:
value = 1
else:
value = self.pre + self.cur
self.pre = self.cur
self.cur = value
self.index += 1
return value这里并没有创建一个保存全部结果的列表,而是每次调用 next() 时才产生下一个斐波那契数。
相对地,如果用普通函数返回列表:
def fibo(total):
if total <= 0:
return []
if total == 1:
return [1]
nums = [1, 1]
for i in range(2, total):
nums.append(nums[-1] + nums[-2])
return nums这种方式会一次性把所有结果存入内存。对于小数据量当然没问题,但当 total 很大时,内存成本会显著上升。
因此,从工程角度讲,当你面临以下场景时,应该优先考虑迭代器式设计:
数据量特别大
结果不一定会全部用完
数据是逐步到达的
需要流式处理
希望降低峰值内存占用
十、生成器:更优雅的迭代器实现方式
手写迭代器虽然原理清晰,但代码往往比较繁琐。你需要显式维护索引、边界、状态推进、异常抛出等细节。为了降低这类模板化代码的编写成本,Python 提供了生成器。
生成器本质上是一种特殊的迭代器,它借助 yield 关键字,让 Python 自动帮你完成状态保存与恢复。
只要一个函数体中出现了 yield,这个函数就不再是普通函数,而是一个生成器函数。调用生成器函数时,函数体不会立刻执行,而是返回一个生成器对象。
例如:
def demo():
print('demo函数开始执行了')
print(100)
yield
a = 200
print(a)
d = demo()
print(d)这里调用 demo() 时,并不会立刻打印任何内容,而只是得到一个生成器对象。换言之,生成器函数的执行是延迟启动的。
这点与普通函数有本质区别。普通函数调用时会立即执行函数体,而生成器函数调用时只创建执行上下文,真正执行要等到你通过 next() 或 send() 去驱动它。
十一、yield 到底做了什么
很多人把 yield 理解为“返回值”,但这只是表象。从运行机制看,yield 做的事情要复杂得多。
当对生成器对象调用 next() 时,生成器函数才开始执行。执行过程中,一旦遇到 yield,就会发生三件事:
第一,当前函数会暂停执行。第二,yield 后面的表达式值会作为这次 next() 的返回结果返回给外部。第三,当前函数的执行状态会被完整保存,包括局部变量、指令位置、栈帧上下文等。
下一次再调用 next() 时,生成器不会从函数开头重新执行,而是从上一次暂停的那个位置继续向下运行,直到遇到下一个 yield 或函数结束。
看下面这个例子:
def demo():
print('demo函数开始执行了')
print(100)
yield '我是第1个yield返回的数据'
a = 200
print(a)
yield '我是第2个yield返回的数据'
b = 300
print(b)
return '执行结束'
d = demo()
r1 = next(d)
print(r1)
r2 = next(d)
print(r2)
try:
next(d)
except StopIteration as e:
print(e)这里第一次 next(d) 会启动函数,运行到第一个 yield 处暂停,并把对应字符串返回。第二次 next(d) 会从上次暂停位置继续运行,直到第二个 yield。第三次继续执行时,函数走到 return,此时生成器结束,并抛出 StopIteration 异常;return 后面的值会作为异常信息附带出去。
因此,生成器的核心不是“返回多个值”,而是构造一个可暂停、可恢复、可逐步产出的执行过程。
十二、生成器对象为什么也是迭代器
从协议角度看,生成器对象天然就满足迭代器的要求。它既有 __iter__(),又有 __next__(),而且 iter(gen) 返回它自己。因此,生成器对象本质上就是一个特殊的迭代器对象。
例如:
def demo():
yield 'A'
yield 'B'
d = demo()
print(hasattr(d, '__iter__'))
print(hasattr(d, '__next__'))
print(iter(d) is d)所以,生成器可以直接用于 for 循环,也可以被 list()、tuple()、set() 等函数消费。它在行为上与普通迭代器兼容,只是在创建方式上更简洁、语义上更自然。
你可以把生成器理解为:Python 用语法层帮你自动生成了一个迭代器类,并自动管理其状态机。
十三、yield 写在循环里,才能体现生成器的强大
生成器最常见也最实用的写法,是在循环中不断 yield 新值。这样就能自然形成“按需产出”的数据流。
例如:
def create_car(total):
for index in range(1, total + 1):
yield f'我是第{index}台车'
cars = create_car(5)这里 cars 不是一个装着 5 个字符串的列表,而是一个生成器对象。你每调用一次 next(cars),它才生成一个新的字符串。这个模型非常适合需要逐条构造结果的场景,比如日志行处理、消息流消费、批量任务分发、分页抓取等。
这种设计的优点在于,它不要求一开始就把全部结果准备好,而是允许“边生产、边消费”。这是一种非常重要的编程思想,在大数据处理、异步编程、协程设计中都有广泛影响。
十四、yield from:把一个可迭代对象整体委托出去
当你在生成器中需要逐个产出另一个可迭代对象中的元素时,可以使用 yield from。它的语义可以理解为“把这个可迭代对象中的元素依次代理产出”。
例如:
def demo():
nums = [10, 20, 30, 40]
yield from nums这相当于:
def demo():
nums = [10, 20, 30, 40]
for item in nums:
yield item从效果上看,yield from 只是语法简化;但从语义上看,它表达的是一种委托生成(delegating generator)的思想。也就是说,当前生成器把部分产出职责交给另一个可迭代对象或子生成器去完成。
在简单示例中,它只是少写几行代码;但在复杂生成器嵌套结构中,yield from 会显著提升代码可读性,并简化子生成器返回值与异常传播的处理。
十五、send():生成器不只是“取值器”,还可以“接收值”
很多文章只讲 next(),却忽略了生成器更高级的一面:它不仅能向外产出值,还能从外部接收值。这正是 send() 的意义。
next(gen) 只能让生成器继续执行,并获取下一个 yield 的产出值。而 gen.send(value) 则更强,它在“恢复执行”的同时,会把 value 传给上一个暂停点的 yield 表达式。
例如:
def demo():
print('demo函数开始执行了')
print(100)
a = yield '我是第1个yield返回的数据'
print(a)
b = yield '我是第2个yield返回的数据'
print(b)
return '执行完成'在这个函数里,a = yield ... 并不是简单地 yield 一个值,它还意味着:下一次恢复时,外部通过 send() 传进来的值会赋给 a。
调用过程如下:
d = demo()
r1 = next(d) # 或 d.send(None)
print(r1)
r2 = d.send(666)
print(r2)
try:
d.send(888)
except StopIteration as e:
print(e)这里第一次启动生成器时,不能发送非 None 值,因为此时生成器尚未运行到任何 yield,还没有“接收位置”。这是使用 send() 时的典型注意事项。
从更高层角度看,send() 说明生成器不是单向的数据输出通道,而可以成为一种双向通信的协作单元。这也是 Python 早期协程模型的重要基础。
十六、用生成器实现自定义遍历,往往比手写迭代器更优雅
如果我们只是想让某个对象支持 for 循环,而不需要显式控制复杂状态机,那么使用生成器通常比手写 __next__() 更自然。
例如,要让 Person 对象可遍历:
class Person:
def __init__(self, name, age, gender, address):
self.name = name
self.age = age
self.gender = gender
self.address = address
self.__attr = [name, age, gender, address]
def __iter__(self):
yield from self.__attr这段代码极其简洁,却完全符合迭代协议。因为 __iter__() 返回的是一个生成器,而生成器对象本身就是迭代器,所以 for 循环可以直接消费。
相比手写迭代器,这种写法有两个明显优势。第一,代码量少,不需要手动维护索引和异常。第二,逻辑更贴近“依次产出数据”这一业务语义。
在绝大多数业务开发场景中,如果你没有特殊需求,优先用生成器实现可迭代行为,通常是更 Pythonic 的选择。
十七、用生成器实现斐波那契数列,更符合按需生产思想
前面已经用类迭代器实现了斐波那契数列。实际上,这类“逐项生成”型问题正是生成器最擅长的场景。
def fibo(total):
pre = 1
cur = 1
for index in range(total):
if index < 2:
yield 1
else:
value = pre + cur
pre = cur
cur = value
yield value这段代码比手写类迭代器短得多,却表达得非常清晰:循环向前推进,每次 yield 一个斐波那契数。
这里体现了生成器非常重要的一种价值:把“状态保存”从程序员手动管理,交给解释器自动管理。你不必自己设计复杂的迭代器对象结构,只需要关注“下一步该产出什么”。
十八、生成器和迭代器都能被一次性展开,但要谨慎
无论是普通迭代器还是生成器,都可以被 list()、tuple()、set() 等内置构造器一次性消费。例如:
f1 = fibo(10)
result = list(f1)
print(result)这当然很方便,尤其是在调试、打印、小规模数据处理时非常常用。但也要明确:一旦你把生成器展开成列表,惰性计算的优势就会消失。因为此时所有结果都被强制求值并全部加载到了内存中。
同时,生成器和迭代器在被展开后往往就被消耗掉了。也就是说,再次遍历时可能已经拿不到任何内容。因此,在实际工程中,如果数据量大、结果流长,最好保持其惰性消费模式,而不要轻易强转为列表。
十九、生成器表达式:列表推导式的惰性版本
除了生成器函数,Python 还提供了另一种快速创建生成器对象的方式:生成器表达式(generator expression)。
它的语法与列表推导式非常相似,只不过外层用的是小括号而不是中括号:
nums = [10, 20, 30, 40]
result1 = [n * 2 for n in nums]
result2 = (n * 2 for n in nums)这里 result1 是一个列表,而 result2 是一个生成器对象。
生成器表达式适合哪些场景?通常是那种每个结果只依赖当前元素、转换逻辑比较简单、又不希望一次性构建全部结果的情况。例如数据映射、轻量过滤、流式管道处理等。
如果你只需要把一批数据做简单变换并逐个消费,生成器表达式会非常优雅。但如果你确实需要随机访问、重复使用、立即获得完整结果,那么列表推导式通常更直观。
因此,两者并不是谁替代谁,而是服务于不同目标:列表推导式强调立即构造,生成器表达式强调惰性产出。
二十、迭代器与生成器的本质关系
到这里,我们可以更系统地总结两者之间的关系。
从抽象层面上说,迭代器是一种协议,是一种接口规范。只要某个对象实现了这套协议,它就是迭代器。生成器则是一种具体实现方式,是 Python 为实现迭代器提供的语言级便利工具。
换句话说:
迭代器是概念与协议
生成器是语法与实现机制
生成器对象本身也是迭代器
如果从工程体验上比较,两者主要差异在以下几个方面。
手写迭代器更适合那些需要精细控制状态机、边界行为、数据访问策略的场景。它的优势在于显式、可控、可扩展,但代价是代码相对冗长。
生成器更适合按步骤产出结果的普通业务逻辑。它依靠 yield 自动维护运行状态,写法简洁,表达自然,非常适合“流式返回”“延迟求值”“逐项处理”的问题。
因此,实际开发中的一个常见经验是:能用生成器解决的遍历问题,优先用生成器;只有当生成器无法满足对状态控制的精细要求时,再考虑手写迭代器类。
二十一、常见误区与易错点
在学习迭代器和生成器时,有几个误区特别值得注意。
第一个误区是把“可迭代对象”和“迭代器”混为一谈。列表是可迭代对象,但通常不是迭代器;iter(list_obj) 的返回结果才是迭代器。理解这一点,才能真正明白 for 循环的工作链路。
第二个误区是忽略迭代器的“消耗性”。很多人会以为迭代器像列表一样可以反复遍历,结果在第二次使用时发现没有数据。根本原因在于迭代器的状态已经推进到结尾。
第三个误区是误以为生成器函数调用后会立刻执行。事实上,只要函数体中有 yield,调用它时得到的是生成器对象,函数并不会马上运行。
第四个误区是错误使用 send()。第一次启动生成器必须使用 next(gen) 或 gen.send(None),否则会抛出异常。
第五个误区是认为“既然生成器省内存,就应该处处使用生成器”。这并不准确。如果你后续需要频繁随机访问数据、重复遍历数据,或者数据规模本来就很小,那么列表反而更直接、更高效、更好用。
二十二、在实际开发中如何选择
当我们把概念讲清楚之后,真正有价值的问题其实是:工程里到底该怎么选。
如果你的目标只是遍历一个现成容器,比如列表、字符串、字典,那么直接写 for 即可,不需要刻意制造迭代器。
如果你有一个自定义对象,希望它能被 for 遍历,最自然的方式通常是在 __iter__() 中返回一个生成器,或者在复杂场景下返回一个独立的迭代器对象。
如果你的数据量非常大,或者你不希望一次性加载全部结果,那么应优先采用迭代器或生成器,利用惰性求值降低内存占用。
如果你的逻辑是“按步骤逐项生成结果”,生成器往往是首选,因为它的表达力和简洁性都更强。
如果你的遍历过程本身需要复杂状态控制、多个指针、特殊回退、缓存策略等,则可以考虑手写迭代器类,以获得更高的行为可控性。
本质上说,选择的标准不是“谁更高级”,而是:你是否需要惰性、是否需要状态控制、是否需要一次性完整结果。
二十三、总结:把三个概念彻底串起来
最后,我们用一套清晰的逻辑链,把本文内容串联起来。
首先,可迭代对象指的是那些能够被 for 循环遍历的对象。它们的职责,是对外提供一个可用于遍历的数据访问入口。
其次,迭代器是负责实际逐个取值的对象。它遵循迭代器协议,能够通过 next() 一步一步向前推进,并在耗尽时抛出 StopIteration。
再次,for 循环本质上就是一个自动调用 iter() 与 next() 的语法结构。它并不依赖某种具体容器,而是依赖统一的迭代协议。
然后,生成器是迭代器的一种高级实现方式。通过 yield,Python 自动替我们维护函数暂停点与局部状态,从而用更自然的方式实现按需产出。
进一步说,迭代器与生成器的共同价值,在于惰性计算与流式处理能力。它们使 Python 在处理大规模数据、复杂管道、增量计算时拥有很强的表达力和工程效率。
因此,如果要用一句话概括本文的核心结论,那就是:
可迭代对象负责“可遍历”,迭代器负责“逐个取值”,生成器负责“更优雅地实现迭代器”。