⌨️ 第五章 从设计到测试
📄 本篇摘要

对象的一生两个仪式:构造函数在出生时初始化(不初始化的对象不能用),析构函数在消亡时收尾。但 Python 的 __del__ 时机不可靠——资源清理的正解是 with 上下文管理器,析构只当兜底。

📝 关系

上一节:DDD 与值对象 | 下一节:WBS 与 EVM

一、构造函数:出生体检

Python 的 __init__ 在 类() 时自动执行,负责把实例的状态摆到位——没初始化的对象等于没体检就上岗。要点:

二、析构函数:不可靠的告别仪式

Python 的 __del__ 在对象被垃圾回收时触发(4-10 讲过:引用计数归零或分代回收)。问题:

资源清理(文件、网络连接、锁)的正解=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 意味着什么?** 答:声明异常已处理、不再向外传播——慎用,容易吞错误。

相关笔记