os.path.splitext只按最后一个点分割,故.tar.gz返回.gz;安全提取需预定义复合后缀列表并按长度降序匹配,用endswith判断后截取。

os.path.splitext 为什么只拆出 .tar.gz 的 .gz?
os.path.splitext 按「最后一个点」切分,不是按扩展名规则解析。它把 "archive.tar.gz" 当成 "archive.tar" + ".gz",完全不管 .tar.gz 是个常见复合后缀。
这导致你用 ext = os.path.splitext(filename)[1] 拿到的永远是 ".gz",而不是你真正想识别的归档类型。
- 它不查注册表、不读文件头、不认 MIME 类型,纯字符串操作
- 对
.tar.bz2、.foo.bar.baz、config.json5同样只返回最后那段 - Windows 下带盘符路径(如
"C:\data\log.txt")也照常只切末尾,不影响逻辑但容易误判驱动器部分
怎么安全提取 .tar.gz 这类多重扩展名?
得自己定义“已知复合后缀列表”,再从长到短匹配。Python 标准库没内置这个逻辑,但实现很轻量。
关键是:先排序后缀列表,按长度降序排,避免 ".gz" 抢在 ".tar.gz" 前面匹配。
立即学习“Python免费学习笔记(深入)”;
- 常用复合后缀建议硬编码:
[".tar.gz", ".tar.bz2", ".tar.xz", ".whl"] - 用
filename.endswith(ext)判断,别用in或正则——前者快且语义准确 - 匹配成功后,用
filename[:-len(ext)]截掉后缀,比反复splitext更可靠
known_exts = [".tar.gz", ".tar.bz2", ".whl", ".pyz"]
filename = "app-1.2.0-py3-none-any.whl"
for ext in sorted(known_exts, key=len, reverse=True):
if filename.endswith(ext):
stem = filename[:-len(ext)]
print(stem, ext) # → "app-1.2.0-py3-none-any" ".whl"
break要不要用 pathlib.Path.suffixes?
pathlib.Path(filename).suffixes 能返回 [".tar", ".gz"],看起来更“结构化”,但它只是机械拆点,不判断合法性。
比如 "notes.v1.backup.txt" 会返回 [".v1", ".backup", ".txt"],而你通常只关心最后那个 ".txt" ——除非你明确在处理版本化备份文件。
-
.suffixes适合做白名单校验(比如确认是否以[".tar", ".gz"]结尾),不适合直接当“主扩展名”用 - 它不处理大小写,
".ZIP"和".zip"在列表里是不同项,得自己统一转小写 - 和
os.path.splitext一样,对隐藏文件(如".gitignore")返回空.suffixes,但.suffix是"",行为不一致
真实项目里推荐怎么选?
取决于你的场景到底要什么:
- 只要快速分出“是不是压缩包”,用预定义复合后缀列表 +
str.endswith最稳 - 要做文件类型路由(比如 .py/.js/.css 分发到不同处理器),优先用
pathlib.Path(filename).suffix.lower(),简单够用 - 需要高精度识别(如区分 .jpg 和 .jpeg,或检测 .png 是否带 alpha),别碰后缀,该上
python-magic或imghdr
别指望一个函数解决所有问题——后缀只是提示,不是事实。真正关键的永远是:你接下来要用这个后缀做什么。










