一、循环(Loop)

本质

循环是一种控制结构,用于重复执行代码块。它本身不是数据结构,而是一种语法机制。

常见形式

1
2
3
4
5
6
7
8
9
# 1. for 循环(遍历可迭代对象)
for item in [1, 2, 3]:
print(item)

# 2. while 循环(基于条件)
i = 0
while i < 3:
print(i)
i += 1

适用场景

  • 需要重复执行相同或相似的操作
  • 遍历容器中的元素(如列表、字典)
  • 实现需要终止条件的逻辑(如用户登录验证)

优势

  • 简单直观,易于理解和使用
  • 适用于大多数重复操作场景

劣势

  • 当处理大量数据时,可能占用大量内存(如生成全量数据列表)
  • 无法暂停或恢复执行流程

二、迭代器(Iterator)

本质

迭代器是实现了迭代器协议的对象(即包含 __iter__()__next__() 方法)。它是一种数据访问方式,允许你按需逐个访问元素。

核心方法

  • __iter__():返回迭代器自身
  • __next__():返回下一个元素,若没有元素则抛出 StopIteration 异常

示例

1
2
3
4
5
6
7
my_list = [1, 2, 3]
my_iter = iter(my_list) # 创建迭代器

print(next(my_iter)) # 输出 1
print(next(my_iter)) # 输出 2
print(next(my_iter)) # 输出 3
print(next(my_iter)) # 抛出 StopIteration

适用场景

  • 需要逐个处理大量数据(如文件读取、数据库查询结果)
  • 实现自定义的遍历逻辑(如树形结构遍历)

优势

  • 节省内存:不需要预先生成所有元素,而是按需生成
  • 支持无限序列(如计数器、斐波那契数列)

劣势

  • 只能向前遍历,不能回退或重置(除非重新创建迭代器)
  • 代码复杂度略高于普通循环

三、生成器(Generator)

本质

生成器是一种特殊的迭代器,它通过更简洁的语法实现(使用 yield 关键字或生成器表达式)。它允许你在需要时生成值,而不是预先计算并存储所有值。

两种形式

  1. 生成器函数(使用 yield):

    1
    2
    3
    4
    5
    6
    7
    def my_generator():
    yield 1
    yield 2
    yield 3

    gen = my_generator()
    print(next(gen)) # 输出 1
  2. 生成器表达式(类似列表推导式,但用圆括号):

    1
    2
    gen = (x for x in range(3))
    print(next(gen)) # 输出 0

适用场景

  • 处理大量数据或无限序列(如日志分析、大数据集)
  • 实现惰性计算(只在需要时生成结果)
  • 简化迭代器的实现(无需手动编写 __iter____next__ 方法)

优势

  • 最节省内存:逐个生成值,不存储整个序列
  • 代码简洁,可读性高
  • 支持暂停和恢复执行(每次 yield 后暂停,下次调用 next() 继续执行)

劣势

  • 只能遍历一次,不能回头
  • 不适合需要随机访问元素的场景

四、对比总结

特性 循环(Loop) 迭代器(Iterator) 生成器(Generator)
本质 控制结构 实现迭代协议的对象 特殊的迭代器
内存效率 低(预生成所有元素) 高(按需生成) 极高(按需生成)
代码复杂度 最低 中等 低(比迭代器更简洁)
状态保存 自动保存(通过 __next__ 自动保存(通过 yield
适用场景 简单重复操作 自定义遍历逻辑 大数据处理、惰性计算
能否无限序列 需手动控制终止条件 支持 支持

五、何时用哪个?

  • 用循环:当需要简单遍历已知的、有限的数据集,且不需要节省内存时。
  • 用迭代器:当需要自定义遍历逻辑,或处理大量数据但不想一次性加载到内存时。
  • 用生成器:当处理超大数据集、无限序列,或需要最大化内存效率时(如数据管道、流式处理)。

举个例子

  • 遍历一个小列表:直接用 for 循环。
  • 遍历一个 10GB 的日志文件:用生成器逐行读取。
  • 实现一个斐波那契数列生成器:用生成器函数(无限序列)。

看完之后可能还会有一个疑问,就是__iter__返回自身,但是好像并没有看到调用__iter__的地方,这是为什么呢?

六、__iter__ 的核心作用

在Python中,迭代器协议要求对象必须同时实现两个方法:

  1. __iter__():返回迭代器自身(即 self
  2. __next__():返回下一个元素,若无元素则抛出 StopIteration

关键点__iter__ 的存在是为了让迭代器对象也能作为可迭代对象使用

七、为什么需要 __iter__ 返回自身?

1. 支持 for 循环和内置函数

Python的 for 循环、list()sum() 等内置函数在使用迭代器前,会先调用 iter() 函数将对象转换为迭代器。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
my_list = [1, 2, 3]
iterator = iter(my_list) # 等价于调用 my_list.__iter__()

# 以下两种写法等价
for item in iterator: # 隐式调用 iter(iterator)
print(item)

# 等价于手动实现的循环
while True:
try:
item = iterator.__next__() # 手动调用 next
print(item)
except StopIteration:
break

关键点:当你将迭代器对象传入 for 循环时,Python会先调用 iter(iterator),此时需要 iterator.__iter__() 返回自身,否则会报错。

2. 支持多重迭代

有些场景需要对同一个迭代器进行多次迭代(虽然不常见)。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class MyRange:
def __init__(self, n):
self.n = n
self.current = 0

def __iter__(self):
return self # 返回自身,支持多重迭代

def __next__(self):
if self.current < self.n:
value = self.current
self.current += 1
return value
else:
raise StopIteration

# 错误示例:重置迭代器状态的错误方式
r = MyRange(3)
for i in r: print(i) # 输出 0, 1, 2
for i in r: print(i) # 无输出,因为迭代器已耗尽

# 正确示例:每次创建新迭代器
class MyRangeFixed:
def __init__(self, n):
self.n = n

def __iter__(self):
return MyRangeIterator(self.n) # 每次返回新的迭代器实例

class MyRangeIterator:
def __init__(self, n):
self.n = n
self.current = 0
def __iter__(self): return self
def __next__(self): ...

r = MyRangeFixed(3)
for i in r: print(i) # 输出 0, 1, 2
for i in r: print(i) # 再次输出 0, 1, 2(因为每次都创建新迭代器)

八、__iter__ 何时被调用?

  1. 显式调用:当你手动调用 iter(iterator)
  2. 隐式调用
    • 使用 for 循环遍历迭代器时
    • 使用内置函数(如 list()sum()tuple() 等)处理迭代器时
    • 在需要可迭代对象的地方使用迭代器时(如解包操作 *iterator

示例

1
2
3
4
5
6
7
8
9
10
11
my_iterator = iter([1, 2, 3])  # 创建迭代器

# 显式调用
explicit_iter = iter(my_iterator) # 调用 my_iterator.__iter__()

# 隐式调用
for item in my_iterator: # 隐式调用 iter(my_iterator)
pass

# 内置函数调用
list(my_iterator) # 调用 iter(my_iterator)

九、为什么你没看到 __iter__ 被调用?

因为 __iter__ 通常是被Python解释器隐式调用的,你在代码中看不到它。但如果没有正确实现 __iter__,以下情况会报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
class BadIterator:
def __init__(self):
self.count = 0
def __next__(self):
if self.count < 3:
self.count += 1
return self.count
raise StopIteration

# 缺少 __iter__,无法用于 for 循环
bad = BadIterator()
for item in bad: # 报错:TypeError: 'BadIterator' object is not iterable
pass

十、__iter__ 的意义

  1. 满足迭代器协议:Python要求迭代器必须同时实现 __iter____next__
  2. 支持与语言特性无缝集成:确保迭代器可以在 for 循环、内置函数等场景中正常工作
  3. 区分迭代器和可迭代对象
    • 可迭代对象(如列表):__iter__ 返回一个新的迭代器
    • 迭代器本身__iter__ 返回自身(self

如果没有 __iter__,迭代器就无法作为可迭代对象使用,Python的许多语法和函数都将无法正常处理它。


看到迭代器跟生成器有这么复杂的功能,那么实际场景当中不免让人想,生成器跟迭代器在循环过程当中可以重置吗?可以反向找到上一个吗?

在 Python 中,生成器默认是单向、不可逆的,但可以通过一些技巧实现“回退”或“重置”的效果。让我分情况解释:

十一、标准生成器:无法回退或重置

生成器函数(使用 yield)或生成器表达式创建的生成器对象,只能向前迭代,无法后退或重置。每次调用 next() 会执行到下一个 yield,状态自动保存,直到生成结束(抛出 StopIteration)。

示例

1
2
3
4
5
6
7
8
9
def count_up_to(n):
for i in range(n):
yield i

gen = count_up_to(3)
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # StopIteration(无法回退到 0 或 1)

十二、实现“回退”功能的方法

1. 缓存已生成的值(手动回退)

将生成器的输出缓存到列表中,需要回退时直接访问列表。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def count_up_to(n):
for i in range(n):
yield i

gen = count_up_to(3)
cache = []

# 正常迭代并缓存
cache.append(next(gen)) # 缓存 0
cache.append(next(gen)) # 缓存 1
print(cache) # [0, 1]

# 回退到之前的值
print(cache[-1]) # 1(回退到上一个值)
print(cache[0]) # 0(回退到第一个值)

2. 自定义生成器类(支持双向迭代)

通过实现 __iter____next__ 方法,结合内部状态管理,实现双向迭代。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class BidirectionalGenerator:
def __init__(self, data):
self.data = data
self.index = 0

def __iter__(self):
return self

def __next__(self):
if self.index < len(self.data):
value = self.data[self.index]
self.index += 1
return value
raise StopIteration

def prev(self):
if self.index > 0:
self.index -= 1
return self.data[self.index]
raise ValueError("Already at the beginning")

# 使用示例
gen = BidirectionalGenerator([1, 2, 3])
print(next(gen)) # 1
print(next(gen)) # 2
print(gen.prev()) # 1(回退)

十三、实现“重置”功能的方法

1. 每次创建新生成器实例

最简单的方法是每次需要重置时重新调用生成器函数。

示例

1
2
3
4
5
6
7
8
9
10
11
def count_up_to(n):
for i in range(n):
yield i

gen = count_up_to(3)
print(next(gen)) # 0
print(next(gen)) # 1

# 重置:重新创建生成器
gen = count_up_to(3)
print(next(gen)) # 0(从头开始)

2. 自定义可重置生成器类

通过添加 reset() 方法重置内部状态。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ResettableGenerator:
def __init__(self, n):
self.n = n
self.reset()

def reset(self):
self.index = 0

def __iter__(self):
return self

def __next__(self):
if self.index < self.n:
value = self.index
self.index += 1
return value
raise StopIteration

# 使用示例
gen = ResettableGenerator(3)
print(next(gen)) # 0
print(next(gen)) # 1

gen.reset()
print(next(gen)) # 0(重置后从头开始)

十四、使用 tee 复制生成器(有限回退)

itertools.tee 可以将一个生成器复制为多个独立的迭代器,允许有限次数的回退。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
from itertools import tee

def count_up_to(n):
for i in range(n):
yield i

gen = count_up_to(3)
gen1, gen2 = tee(gen, 2) # 复制为两个独立迭代器

print(next(gen1)) # 0
print(next(gen1)) # 1

print(next(gen2)) # 0(gen2 独立于 gen1,从头开始)

注意tee 会缓存已生成的值,若原生成器已消耗过多元素,可能导致内存问题。

十五、建议

需求 最佳实现方式 适用场景
少量回退 缓存已生成的值(列表/队列) 数据量较小,需频繁前后移动
完全重置 重新创建生成器实例或自定义 reset() 需多次从头开始迭代
多分支迭代 itertools.tee 需要同时维护多个迭代进度

核心原则:生成器的设计初衷是高效处理一次性数据流,若需要频繁回退或随机访问,建议使用列表等可重复遍历的数据结构。只有在数据量极大且确实需要回退时,才考虑上述优化方法。

看到以上案例,肯定还有人还有疑问,就是这不是在说生成器么,怎么生成器的class当中没有yield?没有yield的话,这是不是就是歌迭代器类了?不是生成器类了?马上回答这个问题

这涉及到 Python 中 生成器(Generator)迭代器(Iterator) 的本质区别。

十六、核心概念区分

1. 生成器(Generator)

  • 定义:一种特殊的迭代器,使用 yield 关键字生成器表达式 创建。
  • 特点
    • 自动实现 __iter____next__ 方法。
    • 状态自动保存(每次 yield 暂停,下次从暂停处继续)。
    • 代码更简洁,无需手动管理状态。

示例

1
2
3
4
5
6
7
8
def gen_numbers():  # 生成器函数(使用 yield)
yield 1
yield 2
yield 3

gen = gen_numbers() # 创建生成器对象
print(next(gen)) # 1
print(next(gen)) # 2

2. 迭代器(Iterator)

  • 定义:实现了 __iter____next__ 方法 的对象。
  • 特点
    • 手动管理状态(如维护索引)。
    • 需显式抛出 StopIteration
    • 代码更复杂,但更灵活。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class NumIterator:  # 迭代器类
def __init__(self):
self.index = 0
self.data = [1, 2, 3]

def __iter__(self):
return self

def __next__(self):
if self.index < len(self.data):
value = self.data[self.index]
self.index += 1
return value
raise StopIteration

it = NumIterator() # 创建迭代器对象
print(next(it)) # 1
print(next(it)) # 2

十七、我们提到的类属于迭代器,而非生成器

当我编写的类(如 ResettableGeneratorBidirectionalGenerator没有使用 yield 关键字 时,它们实际上是 迭代器类,而非生成器类。虽然名字中包含“Generator”,但从技术上讲:

  1. 生成器:由 yield 或生成器表达式创建,是 Python 内置的特殊类型。
  2. 迭代器:通过手动实现 __iter____next__ 方法创建的普通类。

示例对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 生成器(使用 yield)
def generator_example():
yield 1
yield 2

# 迭代器(手动实现协议)
class IteratorExample:
def __init__(self):
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current < 2:
self.current += 1
return self.current
raise StopIteration

十八、为什么会混淆?

  1. 生成器是迭代器的子类:所有生成器都是迭代器(因为自动实现了迭代器协议),但并非所有迭代器都是生成器。
  2. 语义习惯:在日常交流中,有时会用“生成器”泛指任何可迭代对象,导致混淆。
  3. 功能重叠:两者都支持 next()for 循环,但实现方式不同。

十九、何时用迭代器?何时用生成器?

场景 选择 示例
简单的一次性数据流 生成器 python<br>def read_file_lines(file_path):<br> with open(file_path) as f:<br> for line in f:<br> yield line<br>
复杂状态管理(如双向迭代) 迭代器 自定义类实现 prev()reset() 方法
需要实现特定接口(如上下文管理器) 迭代器 类中同时实现 __iter____enter__/__exit__

二十、理解

  1. 生成器:用 yield 创建,自动实现迭代器协议,代码简洁。
  2. 迭代器:手动实现 __iter____next__,更灵活但代码复杂。
  3. 选择标准:优先用生成器(代码简洁),需要高级控制时用迭代器。

当你需要“回退”或“重置”功能时,由于生成器本身不支持这些特性,只能通过迭代器类手动实现。虽然名称可能叫“Generator”,但本质上是迭代器。