os.walk() 慢的根本原因是默认为每个条目调用 stat() 以区分文件和目录,导致百万级小文件遍历时 i/o 爆炸;os.scandir() 通过缓存 direntry 属性避免重复 stat(),配合手动栈实现递归可显著提速。

os.walk() 在遍历数百万文件时慢,根本原因不是它“写得差”,而是它默认每项都调用 stat() 系统调用——而这个操作在大量小文件场景下,I/O 开销直接爆炸。
os.walk() 为什么默认要 stat 每一项?
它需要区分当前条目是文件还是目录,才能决定是否继续递归。所以对 dirs 和 files 列表里的每个名字,它都隐式调用 os.stat() 或 os.lstat()。这不是可选行为,是逻辑必需。
- 即使你只关心文件名(比如批量重命名),
os.walk()仍会为每个条目做一次磁盘 I/O - 在机械硬盘或网络文件系统(如 NFS、SMB)上,单次
stat()延迟可能达几毫秒;百万次就是几千秒 - Python 的 GIL 还会让这些阻塞调用串行排队,无法靠多线程掩盖延迟
os.scandir() 是更轻量的替代方案
Python 3.5+ 的 os.scandir() 把“读目录”和“查属性”拆开:一次系统调用返回带基础属性的 DirEntry 对象,后续 .is_file()、.is_dir() 不再触发新 stat()。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 它本身返回生成器,内存友好
- 关键优势:避免重复
stat()—— 同一个DirEntry多次调用.stat()会缓存结果 - 但注意:
os.scandir()不递归,你需要自己写一层循环来处理子目录
from os import scandir <p>def walk_with_scandir(path): stack = [path] while stack: current = stack.pop() try: with scandir(current) as entries: for entry in entries: if entry.is_dir(): stack.append(entry.path) # 入栈,后续处理 elif entry.is_file(): yield entry.path # 直接产出文件路径 except (PermissionError, OSError): pass # 跳过无权限目录</p>
真正海量文件(>100 万)请绕过 Python 标准库
标准库终究是通用层,没法绕过解释型语言 + GIL + 用户态抽象的固有瓶颈。实测中,os.walk() 在冷缓存下遍历 100 万文件常超 10 秒,而原生命令或 Rust 库可压到 1 秒内。
- Linux/macOS 直接用
find:find /path -type f -print0(配合subprocess读取 null 分隔流) - 跨平台且追求极致性能,用
walkdir(Rust 编写,Python 可通过pyo3绑定调用) - 避免在循环里反复拼路径:用
entry.path而非os.path.join(root, name),前者是预计算好的字符串
真正卡住你的往往不是算法逻辑,而是默认开启的元数据拉取。关掉它,或换一条更贴近系统调用的路径,速度差一个数量级。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










