os.rename()在千万文件场景下卡死或报错,主因是频繁系统调用引发IO瓶颈、句柄耗尽(Linux的ENFILE/EMFILE或Windows的WinError 32)、中文路径乱序及资源管理器隐式锁文件;应改用os.scandir()批量缓冲重命名,并配合shutil.move()绕过锁。

为什么 os.rename() 在千万文件场景下会卡死或报错
直接用 os.rename() 循环处理千万级文件,大概率在几万次后变慢,甚至触发系统级限制:Linux 的 ENFILE(打开文件数超限)或 EMFILE(进程级 fd 耗尽),Windows 则常抛 PermissionError: [WinError 32](文件正被占用)。根本原因不是 Python 慢,而是每次 os.rename() 都触发一次系统调用 + 元数据刷新,且 Python 默认不批量释放目录句柄。更隐蔽的问题是:如果源路径含中文或特殊字符,os.listdir() 在某些 locale 下可能乱序或漏读。
用 os.scandir() + 批量缓冲重命名才真正可行
os.scandir() 返回 DirEntry 对象,自带 name 和 path,避免重复拼接字符串;更重要的是它复用底层目录句柄,比 os.listdir() 快 2–3 倍,内存占用低一个数量级。但关键在「缓冲」:不能每读一个就 rename 一个,而应攒够 1000–5000 个再批量提交。实操建议:
- 用
os.scandir(path)迭代,过滤掉.is_file()为 False 的项(跳过子目录、符号链接) - 对每个
entry,用os.path.join(entry.path, entry.name)构造绝对路径(避免相对路径歧义) - 用列表暂存重命名三元组:
[(old_path, new_path, counter)],满 2000 条就统一执行 rename - 每次 rename 前加
try/except OSError捕获FileExistsError或PermissionError,记录失败项到单独日志文件,不中断主流程
如何安全生成不冲突的新文件名(尤其带序号)
用 range(1, n+1) 直接编号看似简单,但并发 rename 或中断重试时极易覆盖。正确做法是基于文件哈希或原始 mtime 生成唯一前缀,再补序号。例如:
import hashlib
def gen_new_name(old_path, idx):
# 取文件头 1KB 和 mtime 拼接哈希,保证同内容同名,不同文件不同名
with open(old_path, 'rb') as f:
head = f.read(1024)
key = head + str(os.path.getmtime(old_path)).encode()
prefix = hashlib.md5(key).hexdigest()[:8]
return f"{prefix}_{idx:06d}{os.path.splitext(old_path)[1]}"注意:不要用 uuid.uuid4()——它不保证顺序,且无法回溯源文件;也不要依赖 entry.inode(),NTFS 不支持 inode。若业务允许纯递增,务必先用 glob.glob("*.jpg") 获取全量列表再排序,否则 os.scandir() 返回顺序与磁盘物理顺序一致,但不等于字典序。
立即学习“Python免费学习笔记(深入)”;
Windows 下必须绕开资源管理器锁文件的坑
Windows 资源管理器默认为预览图/缩略图缓存打开文件句柄,导致 rename 报错 [WinError 32]。不能靠“等几秒”或重试解决。有效方案只有两个:
- 用 PowerShell 命令预清空缩略图缓存:
ie4uinit.exe -ClearIconCache(需管理员权限,适合部署前) - 在 Python 中改用
shutil.move()替代os.rename()(同分区时底层仍是 rename,但会自动处理部分锁) - 终极方案:启动前用
taskkill /f /im explorer.exe杀掉资源管理器,脚本跑完再用start explorer.exe拉起(适合无人值守服务器)
千万级文件重命名真正的瓶颈从来不在 Python,而在磁盘随机 IO 和文件系统 journal 写入。SSD 上单线程吞吐约 3–5k 文件/秒,HDD 可能跌到 300–800。别迷信多进程——ext4/xfs 对同一目录的并发 rename 有内核锁,反而更慢。老老实实用单进程 + 缓冲 + 错误隔离,比花三天调并发更早跑完。


















