在实际的Python开发中,我们经常需要编写稳定易维护的类:打开文件、连接数据库、启动串口,与硬件交互的上位机开发尤甚。很多问题并不是出在业务逻辑,而是出在对象生命周期管理:何时初始化资源?何时释放?本文聚焦面向对象中的两个关键点——构造函数与析构函数。我们将通过问题分析、可落地的解决方案与可直接复制的代码示例,帮你搭建“创建即可用、销毁不泄露”的类设计,提升你的编程技巧与项目稳定性。
__init__(self, ...),在对象创建后被调用,用于初始化对象状态。注意它不是“真正分配内存”的地方(那是 __new__)。__del__(self),在对象被垃圾回收时“可能”被调用,不保证时机与顺序,尤其在解释器退出阶段。过度依赖会引发不可预期问题。__enter__/__exit__)和 contextlib 才是强烈建议的方式。connect()、open() 等方法,或使用上下文管理器保障释放。__enter__/__exit__,用 with 确保异常也会正确释放。contextlib.ExitStack 简化清理。__del____del__ 里引用全局模块或其他可能已被回收的对象。close()/dispose() 方法__exit__ 中复用该方法,形成单一释放通道。_closed 标记,确保重复释放不会出错(幂等)。延伸学习建议:
contextlib:建议阅读并实战 contextlib.contextmanager、ExitStackPythonfrom pathlib import Path
from typing import Optional, TextIO
class SafeFile:
def __init__(self, path: str | Path, mode: str = "r", encoding: Optional[str] = "utf-8"):
# 仅保存必要参数,不立即打开(最小化构造)
self._path = Path(path)
self._mode = mode
self._encoding = encoding
self._fh: Optional[TextIO] = None
self._closed = True
def open(self):
if not self._closed:
return
self._fh = self._path.open(self._mode, encoding=self._encoding)
self._closed = False
def write_line(self, text: str):
if self._fh is None or self._closed:
raise RuntimeError("File not opened. Call open() or use 'with SafeFile(...) as f:'")
self._fh.write(text + "\n")
self._fh.flush()
def read_all(self) -> str:
if self._fh is None or self._closed:
raise RuntimeError("File not opened.")
return self._fh.read()
def close(self):
if self._closed:
return # 幂等
try:
if self._fh:
self._fh.close()
finally:
self._fh = None
self._closed = True
# 上下文管理协议:推荐的释放路径
def __enter__(self):
self.open()
return self
def __exit__(self, exc_type, exc, tb):
self.close()
# 不吞异常,交给上层处理
return False
# 兜底,不依赖
def __del__(self):
# 尽最大努力释放,不抛异常,不做关键逻辑
try:
self.close()
except Exception:
pass
# 使用示例(适合 Windows 下日志写入)
if __name__ == "__main__":
log_path = "app.log"
with SafeFile(log_path, "a", encoding="utf-8") as f:
f.write_line("启动成功:初始化完成")

要点:
open() 执行实际打开。with 保证异常情况下也会调用 close()。__del__ 仅做兜底,避免阻塞退出。以下示例用伪实现模拟串口,读者可替换为 pyserial 的 serial.Serial。
Pythonimport time
from typing import Optional
class FakeSerial:
def __init__(self, port: str, baudrate: int = 115200, timeout: float = 1.0):
self.port = port
self.baudrate = baudrate
self.timeout = timeout
self._opened = False
def open(self):
if self._opened:
return
# 模拟端口打开
self._opened = True
print(f"[Serial] Opened {self.port} @ {self.baudrate}")
def write(self, data: bytes):
if not self._opened:
raise RuntimeError("Serial not opened")
print(f"[Serial] >> {data!r}")
def read(self, size: int = 1) -> bytes:
if not self._opened:
raise RuntimeError("Serial not opened")
time.sleep(0.05)
return b"OK"
def close(self):
if self._opened:
print(f"[Serial] Closed {self.port}")
self._opened = False
class SerialDevice:
def __init__(self, port: str, baudrate: int = 115200, timeout: float = 0.5):
# 不在构造阶段打开端口,避免抛错导致半初始化
self._port = port
self._baudrate = baudrate
self._timeout = timeout
self._ser: Optional[FakeSerial] = None
self._closed = True
def connect(self):
if not self._closed:
return
self._ser = FakeSerial(self._port, self._baudrate, self._timeout)
self._ser.open()
self._closed = False
def send_cmd(self, cmd: str) -> str:
if self._closed or self._ser is None:
raise RuntimeError("Device not connected. Call connect() or use context.")
payload = (cmd + "\r\n").encode("ascii")
self._ser.write(payload)
resp = self._ser.read(64).decode("ascii", errors="ignore")
return resp
def close(self):
if self._closed:
return
try:
if self._ser:
self._ser.close()
finally:
self._ser = None
self._closed = True
def __enter__(self):
self.connect()
return self
def __exit__(self, exc_type, exc, tb):
self.close()
return False
def __del__(self):
try:
self.close()
except Exception:
pass
if __name__ == "__main__":
# Windows 上位机开发:确保每次通信后释放端口,避免“端口被占用”
with SerialDevice("COM1", 115200) as dev:
print("设备应答:", dev.send_cmd("AT"))

要点:
connect() 与 close(),形成统一释放路径。当一个对象管理多个资源(文件、网络、锁),ExitStack 能自动管理退出顺序与异常。
Pythonfrom contextlib import ExitStack
from pathlib import Path
class MultiResource:
def __init__(self, log_path: str, data_path: str):
self._log_path = Path(log_path)
self._data_path = Path(data_path)
self._stack: ExitStack | None = None
self._log = None
self._data = None
self._closed = True
def open(self):
if not self._closed:
return
self._stack = ExitStack()
try:
self._log = self._stack.enter_context(open(self._log_path, "a", encoding="utf-8"))
self._data = self._stack.enter_context(open(self._data_path, "rb"))
self._closed = False
except Exception:
# 若其中一个打开失败,ExitStack 会帮我们关闭已成功打开的资源
self._stack.close()
self._stack = None
raise
def close(self):
if self._closed:
return
try:
if self._stack:
self._stack.close()
finally:
self._stack = None
self._log = None
self._data = None
self._closed = True
def __enter__(self):
self.open()
return self
def __exit__(self, exc_type, exc, tb):
self.close()
return False
if __name__ == "__main__":
# Example usage
log_path = "app.log"
data_path = "data.bin"
with MultiResource(log_path, data_path) as resources:
resources._log.write("This is a log entry.\n")
c = resources._data.read()
print(f"Data read from file: {c}")

要点:
ExitStack 管理释放顺序。with 包裹关键流程。__del__ 仅兜底,不放关键逻辑,不抛异常。_closed)、记录日志,方便排查。ExitStack。本文围绕 Python 面向对象中的构造函数与析构函数,从问题到方案再到代码实战,给出了可直接应用于实际项目的模板。请记住三点核心要领:
构造函数做“轻初始化”,重操作延迟到显式方法或上下文进入;
使用上下文管理器统一释放资源,__del__ 只作为兜底手段;
保持释放幂等、状态可查、日志可追,特别适用于上位机开发中对串口、文件、网络资源的稳定管理。
将这些编程技巧融入日常的Python开发,你的系统将更稳定、可维护性更高。如果想进一步提升,建议学习 contextlib 与 ExitStack 的高级用法,并在团队内推动代码评审中执行这套规范。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!