《Python编程入门》8.1 可迭代协议、迭代器与生成器
很多人把可迭代对象和迭代器当成同一件事,这恰恰是理解 for 循环与生成器的第一道坎。本节先用一张表把两者的协议差异讲透:可迭代对象只需实现 __iter__,迭代器还要实现 __next__ 并在耗尽时抛出 StopIteration。接着展开 for 循环的等价写法、手写迭代器类、生成器函数与生成器表达式的区别、生成器的惰性与一次性,并实测生成器比列表省下多少内存。
category
很多人把可迭代对象和迭代器当成同一件事,这恰恰是理解 for 循环与生成器的第一道坎。本节先用一张表把两者的协议差异讲透:可迭代对象只需实现 __iter__,迭代器还要实现 __next__ 并在耗尽时抛出 StopIteration。接着展开 for 循环的等价写法、手写迭代器类、生成器函数与生成器表达式的区别、生成器的惰性与一次性,并实测生成器比列表省下多少内存。
本节讲 Python 的上下文管理器与 with 语句。先说明 with 解决的资源成对获取与释放问题,再拆解上下文管理协议的两个特殊方法、退出方法的三个参数,以及返回真值即吞掉异常的机制;随后用 contextlib 的装饰器把生成器改造成上下文管理器,介绍抑制异常与动态管理多个资源两个工具;最后给出 with 管理多资源时的等价展开,并解释文件、锁、数据库连接为何都该做成上下文管理器。
本节讲如何设计一套可维护的异常体系。先讲自定义异常的写法:继承哪个基类、怎样携带结构化字段、如何重写字符串表示;再讲异常链,用真实回溯区分隐式链与显式链,说明何时该用 from 保留原始错误、何时用 from None 切断噪音;随后给出库作者的异常设计原则,包括定义基类、让调用方能精确捕获、不用异常做正常控制流,以及宽泛捕获后重新抛出的正确姿势;最后介绍警告机制与弃用装饰器。
本节讲 Python 的异常机制。先从异常沿调用栈向上传播说起,梳理基类与普通异常的分层结构,解释为什么不要写裸 except、也不该捕获最顶层的基类异常;再用可运行代码演示四段捕获结构的执行顺序、返回值与清理块的交互,以及多重 except 中子类必须写在父类前面的原因。最后介绍异常组与带星号的分支语法、assert 在优化模式下被整体删除的陷阱,以及异常相对返回值的性能代价。
本节讲清魔术方法如何让自定义对象融入 Python 语法:__str__ 与 __repr__ 的分工、定义 __eq__ 后 __hash__ 被置空的陷阱、用容器协议让对象支持 len 与迭代、__call__ 让实例可调用、上下文管理协议,以及 __add__/__radd__/__iadd__ 运算符重载与 __format__ 配合 f-string 的写法。
本节讲清继承语法与 super() 的含义——它不是父类而是 MRO 里的下一个类,并演示菱形问题。接着介绍 abc.ABC 的约束力与组合优于继承的判断标准,再拆解 dataclass 的 field、default_factory、frozen、order、slots 等参数,用选型表对比 dataclass、NamedTuple 与 TypedDict,并演示 PEP 695 泛型类。
本节从 class 语句做了什么讲起,厘清 __init__ 是初始化器而非构造函数,实例属性与类属性的区别及可变类属性被所有实例共享的陷阱;再讲方法绑定中 self 的来源、property 与 setter、staticmethod 与 classmethod 的取舍,用 __mro__ 说清属性查找顺序与 C3 线性化,最后用 tracemalloc 实测 __slots__ 的内存收益。
本节处理导入体系里最易踩坑的三类问题:循环导入的成因与三种真实修法(调整导入位置、TYPE_CHECKING 类型专用导入、把共享部分抽到第三个模块)、PEP 420 命名空间包的机制与适用场景,以及惰性导入的几种做法,含 Python 3.15 的 PEP 810 语法与版本边界。最后用 python -X importtime 看导入与启动时间的关系。
本节聚焦「包」这种组织模块的方式:有 __init__.py 的目录发生了什么、空文件与写满代码的 __init__.py 各有什么代价、from 包.模块 import 名字 如何逐层解析、相对导入「只能在包内使用」的硬约束从何而来。还讲了 __all__ 对 import * 的控制、用 importlib 动态导入,以及预导入在易用性与启动时间间的取舍。
本节从「Python 是怎么找到并加载模块」出发,讲透 import 的三种形式及差异、模块对象与名字绑定的区别、sys.modules 缓存带来的「模块只执行一次」语义,以及 sys.path 的构成与搜索顺序。重点用真实实验对比 python -m 与直接运行脚本时 sys.path[0] 的不同,解释相对导入为何会报错,并说明 __name__ 守卫的原理。
本节核心事实是:Python 里函数是对象,能被赋值、放进容器、当参数传、当返回值,这正是 lambda 与 functools 存在的原因。接着讲清 lambda 的边界与 PEP 8 建议,比较 map/filter 与推导式的取舍,介绍 itemgetter/attrgetter 替代 lambda,最后演示 reduce、singledispatch 与 functools.cache。
本节把函数讲透:从定义与文档字符串说起,拆解位置、默认、*args、仅关键字、**kwargs 五种参数形态,以及调用时用 * 和 ** 解包。重点讲清默认值只在定义时求值一次的陷阱,它和 3.3 节的可变默认参数一脉相承。随后用 LEGB 规则解释名字查找,说明 global 与 nonlocal 的用法并给出闭包示例,最后介绍类型注解、lru_cache 与 partial。
本节讲清 Python 的流程控制与推导式:先梳理哪些值为假,再拆解 if/elif/else、条件表达式、for 与 while、循环 else 子句、break 与 continue,以及 range、enumerate、zip(含 strict=True)。随后用列表、字典、集合、生成器四种推导式替代啰嗦循环,划清嵌套推导式边界,最后介绍 3.10 起的 match/case 模式匹配。
本节揭开 Python 对象模型的核心:变量只是绑定到对象的「名字」,而不是存放值的盒子。我们用 id、is 与 == 的真实实验讲清同一性与相等性的区别,实测小整数缓存与字符串驻留的边界,演示可变默认参数这个经典陷阱,对比 copy.copy 与 copy.deepcopy 在处理嵌套结构与循环引用时的差异,并说明函数传参为什么叫「传对象引用」。
本节系统讲解 Python 的四种内建容器:list、tuple、dict、set。先给出有序性、可变性与查找复杂度的对比表,再分别讲透列表的增删改查与带步长的切片、元组的解包与不可变的真实含义、字典的哈希要求与插入有序、集合的去重与交并差运算,并介绍 collections 中的 deque、Counter、defaultdict 与 namedtuple,最后用实测数据说明何时该换容器。
本节讲透 Python 的四大标量类型:int 的任意精度与位数限制、float 的 IEEE 754 二进制表示误差、bool 作为 int 子类的真相,以及 complex 复数。随后深入字符串的不可变性与 Unicode 码位和字节的区别,系统讲解 f-string 的对齐、精度、千分位与嵌套宽度等格式化语法,并对比 % 与 .format() 的适用场景,为后续容器与文本处理打好基础。
单文件脚本撑不起真实项目。本节先讲一个标准 Python 项目该长什么样,对比 src 布局与平铺布局的取舍,说明 tests、README、.gitignore 各放什么;再拆开 pyproject.toml 的三段结构(build-system、project、tool),讲清 requires-python 的含义;最后贴出 uv init 实际生成的文件内容,让你照着就能搭起项目骨架。
环境装好后,本节带你选编辑器并把解释器指向上节建好的虚拟环境;随后系统讲解 REPL 在 3.13 之后的改进(多行编辑、语法高亮、直接输入 exit 退出),以及 python -c、python -m、python -i 三种命令行用法;最后写出并实际运行第一个脚本,讲清 __name__ 判断这个惯用法的作用,并附命令行速查与常见报错排查清单。
本节从零安装 Python:先对比 macOS、Windows、Linux 三种平台各自的安装方式与坑,再解释 python 与 python3 的历史包袱;随后用一整段讲清「为什么必须用虚拟环境」,手把手创建、激活、退出 venv 并读懂 pyvenv.cfg;最后介绍 pyenv 管理多版本和 uv 这一新一代工具,用真实的 PATH 对比说明「激活」到底改了什么。
本节拆开 CPython 的执行模型:源码经过词法与语法分析生成 AST,再编译成字节码,最后由虚拟机执行。我们会用 ast 打印语法树、用 dis 真实反汇编一段代码,看到 3.14 的新字节码指令与常量折叠。随后对比 CPython、PyPy、实验性 JIT 与自由线程构建的关系,说明「Python 是解释型语言」并不准确。读完本节,你能用 dis 观察任何一段代码在解释器眼里的样子。