作者: andylin02
学习章节: 第8章 - 稳定性与性能
关键词: 异常处理, 资源管理, with, 上下文管理器, 性能分析, 内存优化, slots, 弱引用, 字符串驻留
第8章思维导图:9条建议全景
第8章详细笔记:逐条精讲与源代码
本章聚焦如何写出长期稳定运行且性能优异的 Python 程序。从异常管理到内存优化,每一步都让代码更健壮。
第60条:用 raise 合理利用异常
核心:异常是 Python 处理错误的推荐方式。不要因为过去习惯返回错误码而回避异常。合理使用 try/except 可将错误处理与正常逻辑分离,提高可读性。
# 不推荐:返回错误码让调用方检查
def divide(a, b):
if b == 0:
return None, "division by zero"
return a / b, None
# 推荐:抛出异常,让调用方决定如何处理
def divide(a, b):
if b == 0:
raise ValueError("division by zero")
return a / b
try:
result = divide(10, 0)
except ValueError as e:
print(f"出错: {e}")
异常层次结构:
BaseExceptionSystemExitKeyboardInterruptExceptionValueErrorTypeErrorIOError
关键收获:明确区分正常与异常代码路径,让函数签名只返回成功结果。
第61条:使用 raise from 表达异常链
核心:当在 except 块中抛出另一个异常时,用 raise ... from ... 可以将原始异常作为上下文保留,方便调试时追踪根本原因。
def load_config(filepath):
try:
with open(filepath) as f:
return json.load(f)
except FileNotFoundError as e:
raise ValueError(f"配置文件缺失: {filepath}") from e
try:
load_config("missing.json")
except ValueError as e:
print(e) # 配置文件缺失: missing.json
print(e.__cause__) # FileNotFoundError
若不使用 from:新的异常会丢失原始上下文,使得根因难以定位。使用 raise ... from None 则是有意隐藏上下文。
关键收获:raise ... from ... 让异常形成清晰的因果链,是大型系统排错的关键。
第62条:使用 with 语句管理资源和上下文
核心:with 语句确保无论是否发生异常,资源(文件、锁、网络连接等)都能被正确释放。这远比手写 try/finally 简洁安全。
# 推荐:with 自动关闭文件
with open("data.txt", "r") as f:
content = f.read()
# 自定义上下文管理器:实现 __enter__ 和 __exit__
class ManagedFile:
def __init__(self, name):
self.name = name
def __enter__(self):
self.file = open(self.name, 'w')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
with ManagedFile('hello.txt') as f:
f.write('hello, world!')
__exit__ 参数:三个参数分别代表异常类型、值和回溯。若返回 True,则压制异常。
关键收获:将资源获取和释放封装在 with 中,是避免资源泄漏的最简单方式。
第63条:使用 tracemalloc 追踪内存使用
核心:Python 3.4+ 的 tracemalloc 模块可以跟踪内存分配,显示哪些代码段占用内存最多,是定位内存泄漏和性能瓶颈的利器。
import tracemalloc
tracemalloc.start()
# 模拟内存分配
data = [b'x' * 1024 for _ in range(10000)] # 约10MB
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("Top 10 内存分配位置:")
for stat in top_stats[:10]:
print(stat)
关键收获:tracemalloc 帮助定位意料之外的大对象,是内存调优的第一步。
第64条:使用 timeit 和 cProfile 分析性能
核心:性能优化前必须先测量。timeit 精确测量微小代码块执行时间;cProfile 定位整个程序的性能瓶颈。
# timeit 示例
import timeit
# 比较列表推导与 map 的性能
t1 = timeit.timeit('[x**2 for x in range(1000)]', number=10000)
t2 = timeit.timeit('list(map(lambda x: x**2, range(1000)))', number=10000)
print(f"列表推导: {t1:.3f}s, map: {t2:.3f}s")
# cProfile 命令行用法
# python -m cProfile -s cumtime my_script.py
关键收获:不要靠猜测优化,先让数据说话。cProfile 按累计时间排序,看得最清楚。
第65条:使用 __slots__ 减少内存开销
核心:类的每个实例默认用 __dict__ 存储属性,而这个字典是内存大户。通过在类中定义 __slots__,可固定属性集合,移除 __dict__,极大减少每个实例的内存占用。
class RegularPoint:
def __init__(self, x, y):
self.x = x
self.y = y
class SlottedPoint:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
import sys
rp = RegularPoint(1, 2)
sp = SlottedPoint(1, 2)
print(sys.getsizeof(rp.__dict__)) # 约 104 字节(仅 dict)
print(sys.getsizeof(sp)) # 约 48 字节
代价:
- 不能再随意添加新属性(除非在
__slots__中加入__dict__) - 不支持多继承中的某些组合
关键收获:当要创建大量(百万级)实例时,__slots__ 是必备优化手段。
第66条:了解字符串驻留与 intern
核心:Python 对部分字符串自动驻留(intern),使得相同值的字符串共享同一内存。在代码热路径上,手动调用 sys.intern 可以大幅减少内存并加速字典查找。
import sys
a = "hello world"
b = "hello world"
print(a is b) # 可能为 True(自动驻留)
# 对运行时生成的字符串,手动驻留
c = sys.intern("some long string" * 100)
d = sys.intern("some long string" * 100)
print(c is d) # True
关键收获:在大量字符串比较或作为字典键的场景,intern 能显著提升效率。
第67条:用弱引用防止内存泄漏
核心:强引用(普通引用)使对象不被回收。当某个对象仅需被观察而不应阻止其死亡时,使用 weakref 弱引用。常用于缓存和观察者模式。
import weakref
class ExpensiveObject:
pass
obj = ExpensiveObject()
ref = weakref.ref(obj) # 弱引用
print(ref()) # 返回 obj
del obj
print(ref()) # None,对象已被回收
# WeakValueDictionary 实现自动清空缓存
cache = weakref.WeakValueDictionary()
obj2 = ExpensiveObject()
cache['key'] = obj2
del obj2
print(list(cache.keys())) # 空
关键收获:弱引用让你从“拥有”变为“知道”,打破循环引用,防止内存泄漏。
第68条:正确选择和实现容器
核心:本章(第8章)中关于容器优化的建议:字典和集合的查找是 O(1),远比列表的 O(n) 快。但容器本身也有开销,应根据场景选择合适的结构。
# 需要快速成员测试时用集合
valid_ids = {'A1', 'B2', 'C3'}
if 'A1' in valid_ids: # O(1)
print("找到")
# 需要频繁两端添加删除时用 collections.deque
from collections import deque
queue = deque()
queue.append(1)
queue.appendleft(0)
print(queue.popleft()) # 0
关键收获:选对容器,性能提升往往立竿见影,而不必过早陷入微优化。
性能分析与优化决策流程
第8章总结:稳定与高效的平衡艺术
- 异常即控制流:用
raise抛出异常,用raise from保留上下文,让错误可见、可追踪。 - 资源需守护:所有需要关闭的资源都用
with包裹,无论是否发生异常。 - 先测量,再优化:
timeit测小,cProfile测大,tracemalloc测内存,数据驱动决策。 - 内存即成本:
__slots__省实例字典,weakref破循环引用,intern省重复字符串。 - 容器即速度:集合 O(1) 查找,
deque快速两端操作,选择与需求匹配的结构。
掌握这些原则,你的代码将能长期稳定运行,并在需要时实现精准的性能突破。
下一章预告:第9章 - 测试与调试
在保障稳定性与性能之后,我们进入质量保障的最后一道防线:测试与调试。
你将学到:
- 如何编写有效的单元测试(使用
unittest和pytest) - Mock 依赖,隔离测试目标
- 使用
pdb进行交互式调试的艺术 - 复杂场景下的测试策略
第9章将帮助你构建可靠的测试体系,自信发布每一行代码!
本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

897

被折叠的 条评论
为什么被折叠?



