迭代器和生成器是Python中用于处理可迭代数据的两种重要机制,两者都遵循迭代器协议,但在实现方式、内存占用和使用场景上存在明显差异,理解这些差异能帮助我们写出更高效的代码。

什么是迭代器
迭代器是遵循迭代器协议的对象,核心是通过__next__方法依次返回数据,直到没有更多元素时抛出StopIteration异常。任何实现了__iter__和__next__方法的对象都可以称为迭代器。
我们可以通过自定义类来实现一个简单的迭代器,比如实现一个返回指定范围整数的迭代器:
class RangeIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
# 返回迭代器自身
return self
def __next__(self):
if self.current < self.end:
value = self.current
self.current += 1
return value
else:
# 没有更多元素时抛出异常
raise StopIteration
# 使用自定义迭代器
rit = RangeIterator(1, 4)
for num in rit:
print(num)
运行上述代码会依次输出1、2、3,当current等于end时,调用__next__方法就会抛出StopIteration,迭代过程结束。
什么是生成器
生成器是迭代器的一种简化实现方式,不需要手动实现__iter__和__next__方法,也不需要维护内部状态。生成器有两种常见定义方式:一种是使用生成器函数,函数内部包含yield关键字;另一种是使用生成器表达式,语法类似列表推导式但用圆括号包裹。
生成器函数
生成器函数在调用时不会立即执行函数体,而是返回一个生成器对象,每次调用__next__方法时才会执行到下一个yield语句,返回对应的值并暂停函数执行,保留当前上下文。
用生成器函数实现上述范围整数的功能:
def range_generator(start, end):
current = start
while current < end:
yield current
current += 1
# 使用生成器
rg = range_generator(1, 4)
for num in rg:
print(num)
这段代码的逻辑和自定义迭代器的效果完全一致,但代码量明显更少,不需要手动处理StopIteration异常,yield语句会自动在迭代结束时触发异常。
生成器表达式
生成器表达式是更简洁的生成器定义方式,语法为(表达式 for 变量 in 可迭代对象 if 条件),返回的是一个生成器对象,不会提前生成所有元素。
# 生成器表达式生成1到3的平方
gen = (i * i for i in range(1, 4))
for val in gen:
print(val)
运行后会输出1、4、9,生成器表达式不会像列表推导式那样一次性把所有结果存入内存,而是每次迭代时才计算对应的值。
迭代器与生成器的核心差异
我们可以从以下几个维度对比两者的区别:
| 对比维度 | 迭代器 | 生成器 |
|---|---|---|
| 实现复杂度 | 需要手动实现__iter__和__next__方法,维护内部状态 | 通过yield或生成器表达式实现,无需手动维护状态 |
| 内存占用 | 如果是自定义迭代器,内存占用取决于内部状态的存储 | 惰性计算,只保存当前迭代的状态,内存占用极低 |
| 代码可读性 | 代码逻辑相对分散,需要更多样板代码 | 逻辑更集中,代码更简洁易读 |
| 适用场景 | 需要复杂迭代逻辑,或者需要自定义迭代行为的场景 | 简单的惰性数据生成、大数据流处理场景 |
使用场景建议
如果我们需要处理的数据量很小,或者迭代逻辑非常简单,优先选择生成器,尤其是生成器表达式,能大幅减少代码量。比如需要读取一个大文件的所有行,不需要一次性把文件内容全部读入内存,可以用生成器逐行读取:
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('data.txt'):
# 处理每一行逻辑
pass
如果迭代逻辑比较复杂,比如需要根据之前的迭代结果动态调整后续的返回内容,或者需要对外暴露更多控制方法,那么自定义迭代器会更合适,因为可以在类中封装更多的方法来控制迭代行为。
总结
生成器本质上是迭代器的一种简化实现,所有生成器都是迭代器,但并非所有迭代器都是生成器。日常开发中,优先使用生成器来简化惰性数据生成的代码,只有在需要复杂迭代控制时才考虑自定义迭代器,这样能在保证代码可读性的同时,获得更好的内存使用效率。