zipfile 模块本身不提供“原地删除”(in-place delete)的功能。

你不能直接打开一个 ZIP 文件,删除”其中的某个条目,这是因为 ZIP 文件是一个复杂的归档格式,其目录结构位于文件的末尾,直接在文件中间进行修改会破坏整个归档的完整性。
标准的删除操作流程是:
- 读取源 ZIP 文件。
- 筛选出你想要保留的文件条目(即,排除掉你想要删除的条目)。
- 创建一个新的、临时的 ZIP 文件。
- 将筛选出的文件条目写入这个新的临时 ZIP 文件。
- 替换掉原始的 ZIP 文件(通常需要先删除原文件,再将临时文件重命名为原文件名)。
下面我将分步详细解释这个过程,并提供完整的代码示例。
手动实现删除流程(推荐理解原理)
这种方法能让你清楚地了解删除操作的每一步,非常有助于理解 ZIP 文件的结构。

步骤 1: 准备一个示例 ZIP 文件
我们创建一个名为 my_archive.zip 的示例文件,里面包含一些文件和文件夹。
import zipfile
import os
# 创建一些用于测试的文件
with open("file1.txt", "w") as f:
f.write("This is file 1.")
with open("file2.txt", "w") as f:
f.write("This is file 2.")
with open("subfolder/file3.txt", "w") as f:
f.write("This is file 3 in a subfolder.")
# 将这些文件打包成 ZIP
with zipfile.ZipFile("my_archive.zip", "w") as zf:
zf.write("file1.txt")
zf.write("file2.txt")
zf.write("subfolder/file3.txt")
# 清理测试文件
os.remove("file1.txt")
os.remove("file2.txt")
os.remove("subfolder/file3.txt")
os.rmdir("subfolder")
print("示例 ZIP 文件 'my_archive.zip' 创建成功。")
现在你的目录中应该有一个 my_archive.zip 文件,内容如下:
file1.txt
file2.txt
subfolder/file3.txt
步骤 2: 编写删除函数
下面是一个函数,它接受 ZIP 文件名和要删除的文件/文件夹列表,然后执行上述的删除流程。
import zipfile
import os
import tempfile
def delete_from_zip(zip_name, items_to_delete):
"""
从 ZIP 文件中删除指定的文件或文件夹。
:param zip_name: ZIP 文件的路径。
:param items_to_delete: 一个列表,包含要删除的文件或文件夹的名称。
"""
# 创建一个临时文件
# delete=False 是因为我们会手动管理这个临时文件的删除
with tempfile.NamedTemporaryFile(delete=False) as tmp_zip:
tmp_zip_name = tmp_zip.name
try:
# 使用 'with' 语句确保文件正确关闭
with zipfile.ZipFile(zip_name, 'r') as zin:
with zipfile.ZipFile(tmp_zip_name, 'w') as zout:
# 遍历源 ZIP 文件中的所有文件
for item in zin.infolist():
# 检查当前文件是否在要删除的列表中
# 注意:这里比较的是文件名,要删除文件夹,需要确保文件名以该文件夹名开头
# 并且下一个字符是 '/',以避免误删(例如删除 "docs" 不会误删 "document.txt")
should_delete = False
for name in items_to_delete:
if item.filename == name or item.filename.startswith(name + '/'):
should_delete = True
break
# 如果文件不需要被删除,则将其复制到新的 ZIP 文件中
if not should_delete:
zout.writestr(item, zin.read(item.filename))
# 删除原始 ZIP 文件
os.remove(zip_name)
# 将临时文件重命名为原始 ZIP 文件名
os.rename(tmp_zip_name, zip_name)
print(f"成功从 '{zip_name}' 中删除: {', '.join(items_to_delete)}")
except Exception as e:
print(f"处理过程中发生错误: {e}")
# 如果出错,确保删除可能创建的临时文件
if os.path.exists(tmp_zip_name):
os.remove(tmp_zip_name)
raise
# --- 使用示例 ---
# 假设 'my_archive.zip' 已经存在
delete_from_zip("my_archive.zip", ["file2.txt", "subfolder"])
# 再次调用,删除另一个文件
delete_from_zip("my_archive.zip", ["file1.txt"])
代码解释:
tempfile.NamedTemporaryFile: 创建一个系统命名的临时文件,这是一个安全可靠的做法,可以避免文件名冲突。zin.infolist(): 获取 ZIP 文件中所有文件条目的列表,返回的是一个ZipInfo对象列表,包含了文件名、大小、修改时间等信息。item.filename.startswith(name + '/'): 这是删除文件夹的关键,如果你想删除subfolder,你需要删除所有以subfolder/开头的文件,这样既删除了文件夹本身,也删除了它内部的所有内容。zout.writestr(item, zin.read(item.filename)): 从源 ZIP (zin) 中读取文件内容,并将其写入到新的 ZIP (zout) 中。writestr可以同时写入文件信息和内容。os.remove和os.rename: 用新创建的、内容正确的临时文件替换掉原始的、内容错误的 ZIP 文件。
使用第三方库 zipfile-deflate64(更简单)
如果你觉得手动实现比较繁琐,可以使用第三方库 zipfile-deflate64,它提供了一个 ZipFile 的子类,支持原地修改(包括删除),这在某些场景下效率更高。
首先安装库:
pip install zipfile-deflate64
然后使用起来非常简单:
from zipfile_deflate64 import ZipFile
# 假设 'my_archive.zip' 存在
zip_to_modify = "my_archive.zip"
# 使用 'with' 语句打开 ZIP 文件
# 注意:这里使用 'a' (append) 模式,但 zipfile-deflate64 允许我们进行修改
with ZipFile(zip_to_modify, mode='a') as zf:
# 直接调用 .remove() 方法
zf.remove("file1.txt")
print(f"成功从 '{zip_to_modify}' 中删除 'file1.txt'")
# 你也可以删除文件夹(它会自动删除文件夹内的所有内容)
with ZipFile(zip_to_modify, mode='a') as zf:
zf.remove("subfolder")
print(f"成功从 '{zip_to_modify}' 中删除 'subfolder'")
优点:
- 代码简洁: 直接调用
.remove()方法,逻辑清晰。 - 可能更高效: 如果支持原地修改,可能不需要创建一个全新的临时文件。
缺点:
- 依赖第三方库: �要额外安装和维护。
- 兼容性: 并非所有环境都方便安装第三方库。
总结与选择
| 特性 | 方法一 (手动实现) | 方法二 (zipfile-deflate64) |
|---|---|---|
| 依赖 | 仅使用 Python 标准库 | 需要安装第三方库 |
| 代码量 | 较多,逻辑稍复杂 | 非常简洁,一行代码搞定 |
| 原理 | 创建新文件替换旧文件 | (可能) 原地修改 ZIP 文件 |
| 推荐场景 | 学习原理、无法安装第三方库的项目、追求环境纯净度的项目 | 快速开发、对代码简洁性要求高的项目 |
对于大多数日常任务和脚本,方法一(手动实现) 是最稳妥和通用的选择,因为它不依赖任何外部库,如果你正在构建一个复杂的应用,并且希望代码更优雅,方法二 是一个很好的备选方案。
