1. 迭代器 (Iterator)

1.1 核心概念

迭代器是 Python 中用于迭代操作(即遍历容器元素)的核心工具。它基于迭代器协议,这是一个使对象可迭代的约定。

1.2 迭代器协议

一个对象要成为迭代器,必须实现两个方法:

  • __iter__(self): 返回迭代器对象自身。这是 iter() 函数所调用的方法。

  • __next__(self): 返回容器中的下一个元素。这是 next() 函数所调用的方法。当没有更多元素可供返回时,必须抛出 StopIteration 异常来终止迭代。

1.3 详细解析与示例

示例:

class Countdown:
    def __init__(self, start):
        # 初始化状态:起始计数点
        self.current = start  # 更改变量名以增加可读性,self.start 容易被误解

    def __iter__(self):
        # 返回迭代器自身。for 循环首先会调用此方法获取迭代器。
        return self

    def __next__(self):
        # 核心逻辑:计算并返回下一个值
        if self.current <= 0:
            # 终止条件:抛出 StopIteration 告知循环结束
            raise StopIteration
        value = self.current
        self.current -= 1 # 更新内部状态,为下一次调用 __next__ 做准备
        return value

# 使用迭代器
print("手动调用 next():")
counter = Countdown(3)
print(next(counter))  # 输出: 3
print(next(counter))  # 输出: 2
print(next(counter))  # 输出: 1
# print(next(counter)) # 如果取消注释,会抛出 StopIteration

print("\n使用 for 循环:")
# for 循环的本质:
# 1. 调用 iter(Countdown(3)),即 Countdown(3).__iter__(),获取迭代器对象。
# 2. 重复调用 next()  on that iterator until StopIteration is raised.
for num in Countdown(3):
    print(num)  # 输出: 3, 2, 1

1.4 内置迭代器与 iter() 函数

许多 Python 内置类型都是可迭代对象 (Iterable),但不是迭代器本(如 listtuplestrdict)。你可以使用 iter() 函数从它们那里获取一个迭代器。

my_list = [1, 2, 3]
list_iterator = iter(my_list) # 获取列表的迭代器
print("迭代器")
print(next(list_iterator)) # 1
print(next(list_iterator)) # 2

# for 循环对可迭代对象做的第一件事就是调用 iter()
# 等同于:for item in iter(my_list):
print("for:")
for item in my_list:
    print(item)

1.5 为什么需要迭代器?

迭代器提供了一种统一的访问接口,用于遍历各种不同的数据结构(列表、文件、数据库结果、网络流等),而无需关心其内部实现细节。这就是迭代器设计模式的核心思想。


2. 生成器 (Generator)

2.1 核心概念

生成器是 Python 提供的一种简捷、强大的工具,用于创建迭代器。它通过一种优雅的语法自动实现了迭代器协议,无需像上面那样手动定义一个类。

2.2 生成器函数

生成器函数使用 yield 语句而不是 return 来返回值。

  • 当调用生成器函数时,它并不立即执行函数体,而是返回一个生成器对象(一种迭代器)。

  • 每次对该生成器对象调用 next() 时,函数会从上次 yield 语句暂停的位置继续执行,直到遇到下一个 yield 或函数结束。

  • yield 会暂停函数并记住当前的执行状态(局部变量、指令指针等),下次调用 next() 时从该状态恢复。

2.3 详细解析与示例

def countdown_generator(start):
    print("Generator started!")
    current = start
    while current > 0:
        print(f"Yielding {current}")
        # yield 语句产生一个值,并在此处暂停。
        # 下次调用 next() 时,从下一行代码开始执行。
        yield current
        current -= 1
    print("Generator finished!")

print("创建生成器对象:")
gen = countdown_generator(3) # 打印 Nothing yet! 函数体未执行。
print("Generator object created:", gen)

print("\n开始迭代:")
# 第一次调用 next():从函数开头执行,打印 "Started",进入循环,yield 3 并暂停。
print(next(gen)) # 输出: Generator started! \n Yielding 3 \n 3
# 第二次调用 next():从 `yield current` 的下一行 `current -= 1` 开始,循环,yield 2 并暂停。
print(next(gen)) # 输出: Yielding 2 \n 2
# 第三次调用 next():恢复,`current -= 1`,循环,yield 1 并暂停。
print(next(gen)) # 输出: Yielding 1 \n 1

# 第四次调用 next():恢复,`current -= 1` (now 0), 循环条件不满足,退出循环。
# 打印 "Finished!",函数执行完毕(隐式 return None),引发 StopIteration。
# print(next(gen)) # 会抛出 StopIteration

print("\n使用 for 循环(更常见):")
for num in countdown_generator(3):
    print(f"Received in loop: {num}")
# 输出:
# Generator started!
# Yielding 3
# Received in loop: 3
# Yielding 2
# Received in loop: 2
# Yielding 1
# Received in loop: 1
# Generator finished!

2.4 生成器的优势

  1. 惰性求值 (Lazy Evaluation):元素只在被请求时才生成,而不是一次性在内存中创建整个序列。这对于处理大规模甚至无限的数据流至关重要(例如,读取大文件、生成斐波那契数列无限序列、监听消息队列)。

    def infinite_sequence():
        num = 0
        while True:
            yield num
            num += 1
    
    # 不会创建无限大的列表,只会按需生成数字
    for i in infinite_sequence():
        if i > 5:
            break
        print(i) # 0, 1, 2, 3, 4, 5

  2. 代码简洁:自动处理 __iter____next__StopIteration 和状态保存,逻辑清晰。

  3. 内存高效:同一时间只有一个元素驻留在内存中,极大节省内存空间。

2.5 生成器的方法:.send().throw().close()

生成器对象还提供了更高级的控制方法:

  • .send(value):向生成器“发送”一个值,该值会成为当前 yield 表达式的结果,并继续执行到下一个 yield

  • .throw(exc_type):在 yield 语句处向生成器抛出一个异常。

  • .close():终止生成器。

这些方法使得生成器可以用于实现协程 (Coroutines),这是更复杂的并发编程模式的基础。


3. 迭代器与生成器的关系与区别

3.1 关系

  • 生成器是迭代器的一种特例。所有生成器对象都自动满足了迭代器协议(拥有 __iter__ 和 __next__ 方法)。

  • 你可以把生成器看作是创建迭代器的语法糖更强大的工具

3.2 区别

特性迭代器 (Iterator)生成器 (Generator)
实现方式必须手动定义一个类,实现 __iter__ 和 __next__ 方法。使用包含 yield 关键字的函数定义。
代码简洁性相对冗长,需要显式管理状态(如 self.current)和 StopIteration极其简洁,状态由函数帧自动保存,StopIteration 自动处理。
功能强度基础迭代功能。除了迭代,还支持 .send().throw().close(),可用于协程。
内存使用取决于实现。如果一次性加载所有数据,则内存占用大。天生惰性,内存效率极高,适用于大数据流。
创建目的通常用于为自定义的复杂数据结构提供迭代接口。主要用于按需生成值序列,简化迭代器的创建过程。
普遍性是更基础的概念。是创建迭代器的首选和更常用的方式

核心思想:当你需要迭代一个已存在的集合(如列表、字典)时,使用 iter() 获取它的迭代器。当你需要动态计算并生成一个序列时,几乎总是应该使用生成器。


4. 列表推导式 vs. 生成器表达式

4.1 列表推导式 (List Comprehension)

  • 语法[expression for item in iterable if condition]

  • 特性急切实求值 (Eager Evaluation)。它会立即执行循环,构建并在内存中保存整个结果列表

  • 返回值: 一个完整的 list 对象。

使用场景

  • 当你明确需要并且会多次使用整个结果列表时。

  • 结果集不会太大,可以完全放入内存而不会引起内存问题。

  • 你需要使用列表特有的方法,如 append()pop(), 切片等。

示例

# 立即计算 0 到 999999 所有数的平方,并存储在内存中
# 对于非常大的范围,这会消耗大量内存,甚至导致 MemoryError
squares_list = [x*x for x in range(1000000)]
print(squares_list[10]) # 可以立即随机访问任何元素
print(type(squares_list)) # <class 'list'>
# 文件处理(如果文件不大)
lines = [line.strip() for line in open('data.txt')]
# 可以多次遍历
for line in lines:
    ... 
for line in lines: # 再次遍历
    ...

4.2 生成器表达式 (Generator Expression)

  • 语法(expression for item in iterable if condition) (注意是圆括号 ()

  • 特性惰性求值 (Lazy Evaluation)。它返回一个生成器对象,不会立即计算任何值。值只在迭代时(如 for 循环或 next())按需生成。

  • 返回值: 一个生成器对象

使用场景

  • 处理大规模甚至无限的数据集,无法或不应一次性加载到内存中。

  • 只需要对序列迭代一次

  • 管道式处理,将一个生成器的结果直接传递给另一个函数(如 sum()max()min()),这些函数可以消费迭代器。

  • 结果的中间存储不重要,重要的是计算过程。

示例

# 几乎不占用内存,只是创建了一个生成器对象
# 平方值会在 for 循环每次迭代时才被计算出来
squares_gen = (x*x for x in range(1000000))
print(squares_gen) # <generator object <genexpr> at 0x...>
print(type(squares_gen)) # <class 'generator'>

# 使用:只能迭代一次
total = sum(squares_gen) # sum() 函数会消费这个生成器
print(total)

# 此时 squares_gen 已耗尽,再迭代不会有任何输出
# for num in squares_gen: 
#     print(num) # Nothing

# 处理大文件的最佳实践
# 不会一次性将整个文件读入内存,而是逐行处理
line_lengths = (len(line) for line in open('huge_log_file.txt'))
print(max(line_lengths))

# 管道式处理
nums = (x for x in range(10))
squares = (x**2 for x in nums)
filtered = (x for x in squares if x % 2 == 0)
result = sum(filtered)
print(result)

4.3 关键区别总结

特性列表推导式生成器表达式
求值方式急切,立即执行,构建完整列表。惰性,按需生成元素。
内存占用,存储整个列表。极低,同一时间只处理一个元素。
返回类型listgenerator
速度创建时稍慢(要构建所有元素),但访问快(随机访问)。创建极快,逐个生成元素的速度与列表推导式迭代速度相当。
迭代次数可多次迭代。只能迭代一次,耗尽后即为空。
语法方括号 []圆括号 () (在函数唯一参数时可省略)

经验法则:默认先考虑使用生成器表达式,除非你确实需要一个完整的列表(例如,要进行切片操作或多次使用)。


5.总结

  • 迭代器是提供迭代功能的协议和基础接口

  • 生成器是实现迭代器协议的强大而简洁的工具,通过 yield 实现惰性求值和状态保存,是创建迭代器的首选方式。

  • 列表推导式用于急切地创建列表,适用于结果集小且需多次使用的场景。

  • 生成器表达式用于惰性地创建生成器,适用于处理大数据流或管道计算,极其节省内存

更多推荐