tarfile.add() 没有 exclude 参数,正确方式是使用 filter 参数传入过滤函数,该函数接收 TarInfo 对象并返回 None(跳过)或原对象(保留),需注意路径为相对路径且统一用正斜杠分隔。

tarfile.add() 的 exclude 参数根本不存在
别被文档误导了——tarfile.add() 方法**没有 exclude 参数**。这是新手最常踩的坑,搜到过时教程或误读 API 说明后硬加 exclude=...,结果报 TypeError: add() got an unexpected keyword argument 'exclude'。
真正能控制排除逻辑的是 filter 参数,它接收一个函数,对每个待加入的 tarfile.TarInfo 对象做判断,返回 None 表示跳过,返回原 TarInfo 对象表示保留。
实操建议:
- 写一个过滤函数,检查
tarinfo.name(路径字符串)是否匹配要排除的模式 - 注意路径是相对路径,且用正斜杠分隔(即使在 Windows 上),比如
"logs/error.log"、"__pycache__/main.cpython-311.pyc" - 用
os.path.normpath()或pathlib.PurePath做路径标准化更安全,避免因路径拼接导致匹配失败
用 filter 函数排除单个文件或目录
最常见需求:跳过 .git 目录、venv、或某个临时文件。直接看可运行的最小例子:
立即学习“Python免费学习笔记(深入)”;
import tarfile
import os
def exclude_git_and_pyc(tarinfo):
name = tarinfo.name
if name.startswith('.git/') or name == '.git':
return None
if name.endswith('.pyc') or '__pycache__' in name:
return None
return tarinfo
with tarfile.open('archive.tar.gz', 'w:gz') as tar:
tar.add('myproject/', filter=exclude_git_and_pyc)
关键点:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
-
filter函数必须接收单个tarinfo参数,不能多也不能少 -
tarinfo.name是归档内显示的路径,不含前导/;tarinfo.isfile()、tarinfo.isdir()可用于类型判断 - 如果想排除整个目录,只检查
name == 'dirname'不够——子路径如'dirname/sub/file.txt'还会进来,所以要用startswith('dirname/')
排除多个指定文件名(非通配)的可靠写法
如果只是想排除几个固定名字的文件(比如 config.local.yaml、secrets.env),用集合判断最快最清晰:
EXCLUDE_NAMES = {'config.local.yaml', 'secrets.env', '.env'}
def exclude_by_name(tarinfo):
return None if os.path.basename(tarinfo.name) in EXCLUDE_NAMES else tarinfo
但注意:os.path.basename() 在 tarinfo.name 为 'a/b/.env' 时返回 '.env',没问题;但如果 tarinfo.name == '.env'(顶层文件),也成立。这个逻辑覆盖全。
容易被忽略的陷阱:
- 不要用
in EXCLUDE_NAMES直接查tarinfo.name——因为路径可能带目录前缀,'./.env'就不会命中 - Windows 用户别用
\做分割,tarfile内部统一转为/,硬写反斜杠会导致匹配失效 -
filter函数里抛异常会中断整个打包,调试时建议先 print(name) 看实际路径格式
性能与兼容性提醒:filter 会在每个文件上调用
如果你打包的是几十万小文件的项目,filter 函数会被调用几十万次。虽然本身开销小,但频繁的字符串操作(比如反复 startswith()、endswith())叠加起来可观。
优化建议:
- 把排除规则预编译成正则(仅当模式复杂时),例如
re.compile(r'.(log|tmp)$'),比多次str.endswith()更快 - 避免在
filter里做磁盘 I/O 或网络请求——它本应是纯内存判断 -
filter在 Python 3.2+ 才支持,旧版本只能先生成文件列表再逐个add(),但那样无法保留目录结构元数据
最麻烦的其实是路径边界——比如想排除 docs 目录,但不想误杀 documentation.md。这时候必须严格区分 name == 'docs' 和 name.startswith('docs/'),少一个条件就漏排除或多排除。

















