📄 本篇摘要
对象的一生两个仪式:构造函数在出生时初始化(不初始化的对象不能用),析构函数在消亡时收尾。但 Python 的 __del__ 时机不可靠——资源清理的正解是 with 上下文管理器,析构只当兜底。
一、构造函数:出生体检
Python 的 __init__ 在 类() 时自动执行,负责把实例的状态摆到位——没初始化的对象等于没体检就上岗。要点:
__init__的第一个参数self接住新生的实例;- 必要的校验放构造里(5-24 的 Money 拒绝负数)——坏状态从出生就拦住;
- C++ 还有复制构造函数的细分(拷贝时机不同行为不同,C++ 10.2.2 专篇),Python 对象赋值只是引用(3-14 讲过:拷房号不拷房子)。
二、析构函数:不可靠的告别仪式
Python 的 __del__ 在对象被垃圾回收时触发(4-10 讲过:引用计数归零或分代回收)。问题:
- 时机不确定——有循环引用时可能迟迟不来,解释器退出时顺序也乱;
- 异常被吞——
__del__里抛错只打警告不中断; - 所以官方态度:
__del__别放关键清理逻辑。
资源清理(文件、网络连接、锁)的正解=with 上下文管理器:__enter__ 拿资源、__exit__ 必然释放——不管中途出不出异常(4-8 finally 的思想换了个语法壳)。
三、动手 Demo:一生一世一 with
class TempFile:
"""手动版资源管理:构造拿资源,with 保证释放"""
def __init__(self, path): # 构造:出生体检
if not path.endswith(".tmp"):
raise ValueError("只允许 .tmp 文件")
self.path = path
self.fh = open(path, "w")
print("构造: 已打开", path)
def write(self, text):
self.fh.write(text)
def close(self):
if self.fh and not self.fh.closed:
self.fh.close()
print("已关闭", self.path)
def __enter__(self): # with 进场
return self
def __exit__(self, exc_type, exc_val, tb): # with 出场:异常也必到
self.close()
return False # False = 异常继续向外抛
# 正常路径
with TempFile(".workbuddy/a.tmp") as tf:
tf.write("hello")
print("with 结束,资源已释放")
# 异常路径:中途炸了,__exit__ 照样跑
try:
with TempFile(".workbuddy/b.tmp") as tf:
tf.write("abc")
raise RuntimeError("中途出事")
except RuntimeError as e:
print("捕获:", e, "—— 但文件已关闭")
# 构造校验:坏状态出生即拦
try:
TempFile("bad.txt")
except ValueError as e:
print("构造拦截:", e)
沙箱实跑输出:
构造: 已打开 .workbuddy/a.tmp
已关闭 .workbuddy/a.tmp
with 结束,资源已释放
构造: 已打开 .workbuddy/b.tmp
已关闭 .workbuddy/b.tmp
捕获: 中途出事 —— 但文件已关闭
构造拦截: 只允许 .tmp 文件
看到什么算成功:三段各就各位——正常释放、异常路径也释放、非法参数出生即拦。
读结果:with 块里哪怕抛异常,__exit__ 照样执行(文件必关)——【必然清理】不靠自觉靠语法。构造函数里的校验让非法对象根本没有出生机会。这两件套加起来,__del__ 就只剩理论地位了。
四、30 秒实验
把 __exit__ 里的 return False 改成 return True 重跑——RuntimeError 被吞掉,外面 except 不触发。__exit__ 返回 True=【异常我处理了,别外传】——不熟悉的库代码里突然【不出声】时查这里。
五、常见误区
| 误区 | 真相 |
|---|---|
清理逻辑放 __del__ 最稳 |
时机不可靠、异常被吞;关键清理用 with(上下文管理器) |
__init__ 是创建对象的函数 |
真正创建在 __new__;__init__ 只负责初始化已有实例 |
| 拷贝对象就是赋值 | Python 赋值只拷引用(3-14);要真拷贝用 copy 模块 |
自测三问(点击展开)
1. **为什么关键资源清理不用 __del__?** 答:回收时机不确定、异常被吞;with 的 __exit__ 在退出块时必然执行。 2. **__init__ 和 __new__ 分工?** 答:__new__ 创建实例(单例模式用它拦截),__init__ 初始化该实例。 3. **__exit__ 返回 True 意味着什么?** 答:声明异常已处理、不再向外传播——慎用,容易吞错误。- ☐ 5-25 学习完成
相关笔记
- C++ 10.2 · 构造函数 —— 含复制构造的 C++ 深水区
- C++ 10.3 · 析构函数 —— C++ 析构是确定性的(对比 Python)
- 异常处理 —— finally 与 exit 同一思想
- ⌨️ 编程原理 · 索引 —— 全课路线图