迭代与生成|第六部分:高级Python编程 (Advanced Python)

发布于 2025-09-11 分类: Python
系列文章: Python全方位教程
第 1 部分: Python语言概览|第一部分:Python入门与环境搭建 (Python Foundations) 第 2 部分: 搭建开发环境|第一部分:Python入门与环境搭建 (Python Foundations) 第 3 部分: Python基本语法与规范|第一部分:Python入门与环境搭建 (Python Foundations) 第 4 部分: 数据类型深入解析|第二部分:Python核心数据类型与运算符 (Core Data Types & Operators) 第 5 部分: 运算符大全|第二部分:Python核心数据类型与运算符 (Core Data Types & Operators) 第 6 部分: 条件与循环|第三部分:流程控制与数据结构操作 (Control Flow & Data Structures Manipulation) 第 7 部分: 数据结构高级操作|第三部分:流程控制与数据结构操作 (Control Flow & Data Structures Manipulation) 第 8 部分: 函数|第四部分:函数、模块与代码组织 (Functions, Modules & Code Organization) 第 9 部分: 模块与包|第四部分:函数、模块与代码组织 (Functions, Modules & Code Organization) 第 10 部分: 类与对象|第五部分:面向对象编程 (Object-Oriented Programming - OOP) 第 11 部分: OOP核心特性|第五部分:面向对象编程 (Object--Oriented Programming - OOP) 第 12 部分: 高级OOP主题|第五部分:面向对象编程 (Object-Oriented Programming - OOP) 第 13 部分: 迭代与生成|第六部分:高级Python编程 (Advanced Python) (当前) 第 15 部分: 内存管理与性能|第六部分:高级Python编程 (Advanced Python) 第 16 部分: 文件与目录操作|第七部分:Python标准库精选 (The Standard Library) 第 17 部分: 数据处理与序列化|第七部分:Python标准库精选 (The Standard Library) 第 18 部分: 网络与并发编程|第七部分:Python标准库精选 (The Standard Library) 第 20 部分: 系统交互|第七部分:Python标准库精选 (The Standard Library) 第 21 部分: 数据科学与分析入门|第八部分:Python生态与实战应用 (Ecosystem & Applications) 第 22 部分: Web开发入门|第八部分:Python生态与实战应用 (Ecosystem & Applications) 第 23 部分: GUI编程入门|第八部分:Python生态与实战应用 (Ecosystem & Applications) 第 24 部分: 图像处理入门|第八部分:Python生态与实战应用 (Ecosystem & Applications) 第 25 部分: 自动化脚本|第八部分:Python生态与实战应用 (Ecosystem & Applications) 第 26 部分: Python备忘单 (Cheatsheet)|第九部分:附录与资源 (Appendix & Resources) 第 27 部分: 常见面试题与解答|第九部分:附录与资源 (Appendix & Resources) 第 28 部分: 官方文档与其他学习资源链接|第九部分:附录与资源 (Appendix & Resources)

欢迎来到Python的“性能优化与内存管理”核心区!在此之前,我们处理数据的方式通常是“一次性”的:创建一个列表,把所有元素都放进去,然后处理它。这就像下载一部电影:你必须等整部电影下载完毕,占用了大量硬盘空间后,才能开始观看。

但是,如果有一种方式,能像在线看视频(流媒体)一样处理数据呢?数据源源不断地传来,你看一点,它就加载一点,从不一次性占用你所有的带宽和硬盘。这种“用到才取,按需加载”的哲学,就是本章的核心——迭代与生成

我们将揭开 for 循环的底层秘密,学习如何创建自己的“数据流”,并掌握Python中最高效的编程技巧之一——生成器。这不仅能让你的程序在处理海量数据时避免内存崩溃,更能让你写出简洁、优雅且极具Pythonic风格的代码。让我们开始,学习如何让程序变得“聪明”而“懒惰”!

13.1 迭代器 (Iterator) 与可迭代对象 (Iterable)

这是理解本章所有内容的基础。这两个概念听起来很像,但有着本质的区别。

  • 可迭代对象 (Iterable):

    • 是什么? 任何你可以用 for 循环来遍历的对象。比如列表、元组、字典、集合、字符串等。
    • 如何判断? 从技术上讲,一个对象只要实现了 __iter__() 方法,它就是可迭代的。这个方法会返回一个迭代器。
    • 比喻: 一个**“容器”或一本“书”**。它包含了所有的数据,但它自己不动手去翻页。
  • 迭代器 (Iterator):

    • 是什么? 一个代表数据流的对象。它会记住当前遍历的位置。
    • 如何判断? 一个对象只要实现了 __next__() 方法(返回下一个数据)和 __iter__() 方法(返回它自己),它就是迭代器。
    • 比喻: 一个**“书签”“阅读指针”**。它知道你当前读到了哪一页,并且能帮你翻到下一页。当它翻到书的末尾时,会告诉你“没内容了”(通过引发 StopIteration 异常)。

for 循环的真相

当你写下 for item in my_list: 时,Python在幕后做了这些事:

  1. 调用 my_list__iter__() 方法,获得一个迭代器对象。
  2. 进入一个无限循环,在循环的每一次:
  3. 调用这个迭代器对象的 __next__() 方法来获取下一个元素。
  4. __next__() 方法引发 StopIteration 异常时,for 循环捕捉到这个异常,并优雅地结束循环。

手动模拟 for 循环:

my_list = [1, 2, 3] # my_list 是一个可迭代对象 (Iterable)

# 1. 从可迭代对象获取迭代器
my_iterator = iter(my_list) # iter() 函数内部就是调用 my_list.__iter__()

print(type(my_list))    # <class 'list'>
print(type(my_iterator))# <class 'list_iterator'>

# 2. 手动调用 next()
print(next(my_iterator)) # 输出: 1
print(next(my_iterator)) # 输出: 2
print(next(my_iterator)) # 输出: 3

# 3. 再次调用,迭代器耗尽,引发 StopIteration 异常
# print(next(my_iterator)) # 这行会报错: StopIteration

理解了这个机制,你就理解了Python迭代模型的精髓!

13.2 生成器 (Generator):最优雅的迭代器工厂

手动创建一个同时实现 __iter____next__ 的类来做迭代器,有点麻烦。Python提供了一种绝妙的“语法糖”来轻松创建迭代器,那就是生成器

生成器函数

一个函数只要包含了 yield 关键字,它就不再是一个普通的函数,而是一个生成器函数。调用它不会立即执行函数体,而是返回一个生成器对象(它本身就是一种迭代器)。

yield 关键字的魔力在于:

  • 它会“产出”一个值,就像 return 一样。
  • 但它不会结束函数,而是暂停函数的执行,并保存当前所有的状态(局部变量等)。
  • 当下次对生成器调用 next() 时,函数会从上次暂停的地方继续执行

案例:一个简单的倒计时生成器

def countdown(n):
    print("生成器开始执行...")
    while n > 0:
        yield n
        n -= 1
    print("生成器执行完毕。")

# 调用生成器函数,返回一个生成器对象
my_countdown = countdown(3)
print(my_countdown) # 输出: <generator object countdown at 0x...>

# 每次调用 next(),它就执行到下一个 yield
print(f"倒计时: {next(my_countdown)}") # 输出: 生成器开始执行... \n 倒计时: 3
print(f"倒计时: {next(my_countdown)}") # 输出: 倒计时: 2
print(f"倒计时: {next(my_countdown)}") # 输出: 倒计时: 1

# 再次调用,函数执行完毕,自动引发 StopIteration
# next(my_countdown) # 输出: 生成器执行完毕。 \n 触发 StopIteration 异常

# 通常我们用 for 循环来自动处理这一切
for i in countdown(5):
    print(i)

生成器的巨大优势:内存效率
想象一下,你需要一个包含1到1亿所有数字的平方的列表。

import sys

# 列表推导式:一次性在内存中创建所有元素
huge_list = [i*i for i in range(10000000)]
print(f"列表占用的内存: {sys.getsizeof(huge_list)} 字节")

# 生成器表达式:几乎不占用内存
huge_generator = (i*i for i in range(10000000))
print(f"生成器占用的内存: {sys.getsizeof(huge_generator)} 字节")

你会看到,列表占用了几百MB的内存,而生成器只占用了极小的、固定大小的内存。因为生成器根本没有存储这些数字,它只知道“如何生成下一个数字”。

生成器表达式

这是列表推导式的“生成器版本”。语法上,只需将 [] 换成 ()

# 列表推导式
my_list = [x * 2 for x in range(5)] # [0, 2, 4, 6, 8]

# 生成器表达式
my_gen = (x * 2 for x in range(5)) # <generator object <genexpr> at 0x...>

for item in my_gen:
    print(item)

当你只需要对序列进行一次迭代,而不需要把所有结果都存起来时,生成器表达式是你的不二之选。

13.3 协程 (Coroutine):异步编程基础

协程是生成器概念的延伸,是一种更为强大的存在。

  • 生成器是数据的生产者 (Producer),它 yield 数据给调用者。
  • 协程既可以是生产者,也可以是消费者 (Consumer)。它可以通过 (yield) 表达式来接收外部通过 .send() 方法发送过来的数据。

这使得函数可以在执行的中途暂停,去执行其他函数,在适当的时候再切换回来继续执行,从而实现了协作式多任务,是Python中异步编程 (async/await) 的基石。

一个简单的协程消费者案例:

def simple_coroutine():
    print("协程已启动,准备接收数据...")
    try:
        while True:
            # (yield) 会暂停在这里,等待外部发送数据
            data = (yield)
            print(f"协程收到了: {data}")
    except GeneratorExit:
        print("协程关闭。")

# 创建协程
coro = simple_coroutine()

# 关键第一步:启动协程
# 必须先调用一次 next() 或 send(None),让它执行到第一个 yield 表达式
next(coro) # 或者 coro.send(None)

# 使用 .send() 方法向协程发送数据
coro.send("Hello")
coro.send(123)
coro.send([1, 2, 3])

# 关闭协程
coro.close()

运行结果:

协程已启动,准备接收数据...
协程收到了: Hello
协程收到了: 123
协程收到了: [1, 2, 3]
协程关闭。

虽然这个例子很简单,但它展示了核心思想:yield 不仅能向外“吐”数据,还能作为表达式接收从 .send() “喂”进来的数据。这种双向通信的能力,是实现复杂异步流程的关键。现代Python的 async/await 语法,正是建立在协程这个底层概念之上的高级封装。


太棒了!你已经深入到了Python执行模型的内部,理解了for循环的本质,并掌握了“懒惰”编程的艺术。你现在知道:

  • 可迭代对象是数据的容器,迭代器是遍历它的指针。
  • 生成器是创建迭代器的最优雅、最高效的方式,它通过 yield 关键字实现状态的暂停与恢复。
  • 生成器的核心优势在于内存效率和处理无限数据流的能力。
  • 协程是生成器的超集,能够接收外部数据,是异步编程的基石。

你对Python的理解已经从“如何使用”上升到了“为何如此”的层面。在下一章,我们将继续探索Python的高级函数与特性,比如闭包、上下文管理器 (with语句)等,这些工具将进一步磨砺你的代码,使其更加健壮、安全和富有表现力。准备好,继续你的Python深潜之旅!


-- 感谢阅读 --