
本文介绍在 Python 中使用 zipfile 模块解压 ZIP 文件时,如何跳过指定文件(如正在运行的可执行程序)并完整提取其余所有文件,避免因逐个调用 extract() 导致的异常中断或路径问题。
本文介绍在 python 中使用 `zipfile` 模块解压 zip 文件时,如何跳过指定文件(如正在运行的可执行程序)并完整提取其余所有文件,避免因逐个调用 `extract()` 导致的异常中断或路径问题。
在开发自更新程序(如基于 wxPython 的桌面应用)时,一个常见需求是:下载新版 ZIP 包后,解压其中所有文件,但必须跳过当前正在运行的主程序(例如 updater.exe),否则会导致“文件正被占用”错误或进程崩溃。
你最初的代码看似合理:
with ZipFile('wotu.zip', 'r') as f:
files = f.namelist()
dlg = wx.ProgressDialog('Updating', 'Extracting files...', len(files))
progress = 0
for file in files:
if file != 'updater.exe':
print(file)
f.extract(file) # ⚠️ 问题根源在此
progress += 1
dlg.Update(progress)但该方式存在两个关键隐患:
- ZipFile.extract() 在某些 ZIP 结构下可能意外终止循环(尤其当 ZIP 包含目录、空文件或特殊元数据时);
- 更重要的是:extract(file) 默认解压到当前工作目录,但不会自动创建缺失的父目录——若 ZIP 中存在嵌套路径(如 lib/utils.py),而 lib/ 目录尚不存在,extract() 将抛出 FileNotFoundError 并中断整个流程,表现为“只解压了一个文件”。
✅ 正确解法是:先筛选需解压的文件列表,再统一调用 extractall(members=...)。extractall() 内部会自动处理目录创建、路径安全和错误聚合,健壮性远高于手动循环 extract()。
优化后的推荐实现如下:
import zipfile
import wx
with zipfile.ZipFile('wotu.zip', 'r') as f:
all_files = f.namelist()
# 过滤掉需要跳过的文件(支持多个)
members_to_extract = [name for name in all_files if name != 'updater.exe']
dlg = wx.ProgressDialog(
'Updating',
'Extracting files...',
maximum=len(members_to_extract),
style=wx.PD_AUTO_HIDE | wx.PD_CAN_ABORT
)
# extractall 自动处理目录创建与路径安全
f.extractall(members=members_to_extract)
# 若需实时进度(extractall 不提供回调),可改用 extract() + os.makedirs 防错
# 但通常 extractall + 预筛选已足够可靠? 关键注意事项:
- members 参数必须是 namelist() 返回的原始字符串列表,不可修改原 ZIP 对象内的文件状态;
- extractall() 会覆盖同名文件,符合“其余文件全部更新”的需求;
- 若 ZIP 包含绝对路径或 ../ 路径(恶意 ZIP),建议添加路径校验(见下方防御式写法);
- wx.ProgressDialog.Update() 无法直接与 extractall() 同步,如需精确进度,应改用 extract() + 手动确保目录存在:
import os
for file in members_to_extract:
# 安全解压:先创建父目录
target_path = os.path.join('.', file)
os.makedirs(os.path.dirname(target_path), exist_ok=True)
f.extract(file, path='.') # 显式指定解压根目录
dlg.Update(dlg.GetRange()) # 或按索引更新总结:避免逐个调用 extract() 处理复杂 ZIP;优先使用 extractall(members=...) 配合列表推导式筛选,兼顾简洁性、健壮性与安全性。对于生产级自更新模块,还应增加 ZIP 签名校验、解压前备份、异常回滚等机制。

















