Python rstrip 去扩展名时误删文件名末尾字符
rstrip 按字符集合删除而不是按后缀匹配;改用 removesuffix 或受条件保护的切片,并区分任意扩展名与固定后缀。
处理 CSV 文件名时,name.rstrip('.csv') 看起来能去掉扩展名,但 results.csv 变成了 result,文件主体最后的 s 也丢了。换几个文件名又正常,容易误以为输入文件有隐藏字符。
rstrip 接收的是一组可删除字符,不是完整后缀。它会从右侧持续删除属于 .csv 这四个字符集合的字符,直到遇到集合外的字符才停止。删除 .csv 后,若主体还以 s、c、v 或句点结尾,也会继续删除。
只删除一次指定后缀
在 Python 3.9 及以后,用 removesuffix 表达“仅在末尾存在这个完整后缀时删除一次”:
names = [
"results.csv",
"report.v2.csv",
"backup.csv.csv",
"notes.txt",
]
base_names = [name.removesuffix(".csv") for name in names]
print(base_names)
按规则,结果应为:
['results', 'report.v2', 'backup.csv', 'notes.txt']
backup.csv.csv 只删除最后一次 .csv;不以 .csv 结尾的名称保持原样。这个示例只计算字符串,不会重命名或修改任何文件。
匹配区分大小写,report.CSV 不会被 .removesuffix('.csv') 修改。如果业务允许几种大小写扩展名,应另外制定匹配规则,不能为了后缀判断把整个文件名统一转小写;在区分大小写的文件系统中,那会改变路径含义。
旧版 Python 先判断,再切片
维护 Python 3.8 或更早代码时,不能直接添加 removesuffix。可以保留等价的条件判断:
def remove_suffix(text, suffix):
if suffix and text.endswith(suffix):
return text[:-len(suffix)]
return text
base_name = remove_suffix("results.csv", ".csv")
这里的 suffix 非空检查有实际作用:空后缀的长度是零,text[:-0] 等同于 text[:0],会返回空字符串。函数在后缀为空时保留原值,与删除空后缀的目标一致。
不要不加判断地切掉最后四个字符;不是 CSV 的输入也可能被截断。也不要用 replace('.csv', ''),因为它默认删除所有位置上的匹配,文件主体中的 .csv 也会被移除。
去掉任意最后扩展名是另一种规则
如果需求确实是移除最后一个扩展名,而不限于 .csv,可以用路径接口:
import os.path
root, extension = os.path.splitext("report.v2.csv")
print(root, extension)
预期分别为 report.v2 和 .csv。这个接口保留主体中较早的句点;archive.tar.gz 的最后扩展名是 .gz,不是整个 .tar.gz。
点开头的文件还有自己的规则:例如 .env 通常会被当作没有扩展名的名称,不会被拆成空主体与 .env 扩展名。因此,splitext 与删除固定字符串后缀不能无条件互换。
name.split('.')[0] 也不适合代替这两种规则。它会在第一个句点截断,report.v2.csv 就只剩 report。当输入包含目录路径时,目录名中的句点还会进一步干扰这种写法。
在执行文件操作前检查名称映射
至少对照主体以 s、c、v 结尾、主体含句点、重复后缀、无后缀、不同大小写后缀等输入。检查完整的旧名称到新名称映射,而不是只看数量或抽一个正常文件。
如果计算结果会被用于批量重命名,还需要单独检查目标重名与已有文件冲突。字符串处理正确并不代表后续文件操作安全;先保存映射和必要备份,再执行明确范围内的变更。
回退这次字符串改动不会自动把已经重命名的文件改回来。保留原名称映射,才能按实际完成的操作恢复;不要再用另一轮猜测性的后缀处理反推原文件名。


