Python目录遍历一般用到os.walk函数,下面详细拆解os.walk函数,仅供参考。
一、os.walk函数基础语法
os.walk函数的作用是遍历指定目录及其所有子目录,返回一个三元组(根目录,子目录列表,文件列表),基础语法如下:
os.walk(top, topdown=True, onerror=None, followlinks=False)
1. 参数
- top (必填): 要遍历的根目录路径(字符串类型)。
- topdown (可选):
- True (默认): 自顶向下遍历(先父目录后子目录)。
- False: 自底向上遍历(先子目录后父目录),适用于删除空目录等场景。
- onerror (可选):
- 异常回调函数,接收一个OSError参数,用于处理权限错误等。
- followlinks (可选):
- False (默认): 不跟踪符号链接。
- True: 跟踪符号链接指向的目录(需防循环引用)。
2. 返回值
返回一个生成器,每次迭代生成三元组:
- dirpath: 当前目录的完整路径(字符串)。
- dirnames: 当前目录下的子目录名列表(不含路径)。
- filenames: 当前目录下的文件名列表(不含路径)。
二、应用场景示例
1. 基础遍历与路径拼接
遍历目录并打印所有文件完整路径:
import os
for root, dirs, files in os.walk('example_folder'):
for file in files:
full_path = os.path.join(root, file)
print(full_path)
这段代码的作用是遍历指定目录(example_folder)及其所有子目录,并打印出每个文件的完整路径。
具体来说:
- os.walk('example_folder') 会生成一个三元组 (root, dirs, files),其中:
- root 是当前遍历的目录路径。
- dirs 是当前目录下的子目录列表。
- files 是当前目录下的文件列表。
- 外层循环 for root, dirs, files in os.walk('example_folder') 遍历每个目录及其子目录。
- 内层循环 for file in files 遍历当前目录下的每个文件。
- os.path.join(root, file) 将目录路径和文件名拼接成文件的完整路径。
- print(full_path) 打印出文件的完整路径。
示例输出(假设 example_folder 包含以下文件和子目录):
example_folder/file1.txt
example_folder/subdir/file2.txt
example_folder/subdir/subsubdir/file3.txt
如果你希望遍历其他目录,只需将 'example_folder' 替换为你的目标目录路径即可。
2. 文件过滤与类型搜索
查找所有.py文件:
import os
py_files = []
for root, _, files in os.walk('/project'):
for file in files:
if file.endswith('.py'):
py_files.append(os.path.join(root, file))
print(f"找到 {len(py_files)} 个Python文件")
这段代码的作用是遍历 /project 目录及其所有子目录,查找所有以 .py 结尾的 Python 文件,并将它们的完整路径存储在 py_files 列表中,最后打印出找到的 Python 文件数量。
具体分析:
- os.walk('/project') 生成一个三元组 (root, dirs, files),其中:
- root 是当前遍历的目录路径。
- dirs 是当前目录下的子目录列表(这里用 _ 忽略,由于不需要)。
- files 是当前目录下的文件列表。
- 外层循环 for root, _, files in os.walk('/project') 遍历 /project 目录及其所有子目录。
- 内层循环 for file in files 遍历当前目录下的每个文件。
- if file.endswith('.py') 检查文件名是否以 .py 结尾(即是否是 Python 文件)。
- py_files.append(os.path.join(root, file)) 将符合条件的 Python 文件的完整路径添加到 py_files 列表中。
- print(f”找到 {len(py_files)} 个Python文件”) 打印出找到的 Python 文件数量。
示例输出(假设 /project 目录下有 3 个 Python 文件):
找到 3 个Python文件
如果你希望遍历其他目录,只需将 /project 替换为你的目标目录路径即可。
3. 目录排除技巧
跳过隐藏目录(如.git):
import os
for root, dirs, files in os.walk('.'):
# 原地修改dirs以跳过隐藏目录
dirs[:] = [d for d in dirs if not d.startswith('.')]
for file in files:
print(os.path.join(root, file))
这段代码的作用是遍历当前目录(.)及其所有子目录,并打印出每个文件的完整路径,同时跳过所有隐藏目录(即名称以 . 开头的目录)。
具体分析:
- os.walk('.') 生成一个三元组 (root, dirs, files),其中:
- root 是当前遍历的目录路径。
- dirs 是当前目录下的子目录列表。
- files 是当前目录下的文件列表。
- dirs[:] = [d for d in dirs if not d.startswith('.')] 是一个原地修改操作,它会过滤掉 dirs 列表中所有以 . 开头的目录(即隐藏目录),从而在后续的遍历中跳过这些目录。
- dirs[:] 表明对 dirs 列表进行原地修改(而不是创建新列表)。
- [d for d in dirs if not d.startswith('.')] 是一个列表推导式,筛选出不以 . 开头的目录。
- for file in files 遍历当前目录下的每个文件。
- print(os.path.join(root, file)) 打印出文件的完整路径。
示例输出(假设当前目录结构如下):
.
├── .hidden_dir/ # 隐藏目录,会被跳过
│ └── file1.txt
├── normal_dir/
│ ├── file2.txt
│ └── .hidden_file # 隐藏文件,不会被跳过
└── file3.txt
运行代码后,输出将是:
./normal_dir/file2.txt
./normal_dir/.hidden_file
./file3.txt
注意:
- .hidden_dir/ 被跳过,因此其下的 file1.txt 不会被打印。
- .hidden_file 是文件(不是目录),因此不会被跳过,会被打印。
适用场景:
- 需要遍历目录时跳过隐藏目录(如 .git、.idea、.DS_Store 等)。
- 适用于清理或统计非隐藏文件的情况。
4. 后序遍历与目录清理
自底向上删除空目录:
import os
for root, dirs, files in os.walk('.', topdown=False):
for file in files:
os.remove(os.path.join(root, file))
if not os.listdir(root): # 检查目录是否为空
os.rmdir(root)
这段代码的作用是递归删除当前目录(.)及其所有子目录和文件,采用自底向上的遍历方式(topdown=False),确保先删除文件和子目录,再删除父目录。
代码解析:
- os.walk('.', topdown=False)
- . 表明当前目录。
- topdown=False 表明自底向上遍历(先子目录,后父目录),这是删除操作的关键,由于需要先删除子目录中的文件和目录,再删除父目录。
- for file in files:
- 遍历当前目录下的所有文件。
- os.remove(os.path.join(root, file)) 删除当前文件。
- if not os.listdir(root):
- 检查当前目录是否为空(os.listdir(root) 返回目录内容,如果为空则返回空列表)。
- 如果为空,则执行 os.rmdir(root) 删除该目录。
注意事项:
- 危险操作!
- 这段代码会永久删除当前目录及其所有内容,请谨慎使用!
- 提议先备份重大数据,或用 print 取代 os.remove 测试输出路径。
- 权限问题
- 如果目录或文件被占用(如被其他程序使用),删除会失败并抛出异常(如 PermissionError)。
- 符号链接(Symlinks)
- 默认情况下,os.walk 会遍历符号链接指向的目录。如果需要跳过符号链接,可以添加 followlinks=False(但 Python 3.5+ 默认已禁用)。
改善:
- 添加日志或测试模式(先打印路径,确认无误后再删除):import os
for root, dirs, files in os.walk('.', topdown=False):
for file in files:
print(f”将删除文件: {os.path.join(root, file)}”) # 测试用
# os.remove(os.path.join(root, file)) # 实际删除
if not os.listdir(root):
print(f”将删除目录: {root}”) # 测试用
# os.rmdir(root) # 实际删除 - 处理异常(如权限不足):try:
os.remove(os.path.join(root, file))
except PermissionError as e:
print(f”无法删除 {file}: {e}”)
适用场景:
- 彻底清空目录(如临时目录清理)。
- 需要递归删除的场景(如测试环境重置)。
请确保理解代码行为后再运行!
5. 符号链接处理
跟踪符号链接并避免循环:
import os
visited = set()
for root, dirs, files in os.walk('/', followlinks=True):
real_path = os.path.realpath(root)
if real_path in visited:
dirs[:] = [] # 跳过已访问目录
else:
visited.add(real_path)
这段代码的作用是遍历根目录(/)及其所有子目录(包括符号链接),但通过 os.path.realpath() 检查并跳过重复的目录(如符号链接指向的同一目录)。这样可以避免无限循环或重复处理符号链接导致的重复目录。
代码解析:
- visited = set()
- 初始化一个集合,用于存储已访问的目录的真实路径(解析符号链接后的路径)。
- for root, dirs, files in os.walk('/', followlinks=True):
- os.walk('/') 从根目录开始遍历。
- followlinks=True 允许跟随符号链接(否则会跳过符号链接指向的目录)。
- root 是当前遍历的目录路径(可能是符号链接)。
- dirs 是当前目录下的子目录列表(会被原地修改)。
- files 是当前目录下的文件列表(未使用)。
- real_path = os.path.realpath(root)
- 解析 root 的真实路径(如 /a -> /b,则 real_path 为 /b)。
- 这样能检测到符号链接指向的同一目录。
- if real_path in visited:
- 如果 real_path 已被访问过,说明当前目录是重复的(可能是符号链接)。
- dirs[:] = [] 清空 dirs 列表,使 os.walk 跳过该目录的子目录遍历。
- else: visited.add(real_path)
- 如果 real_path 未被访问过,将其加入 visited 集合,并继续正常遍历。
适用场景:
- 遍历包含符号链接的目录结构时,避免重复处理同一目录。
- 例如,/a -> /b 和 /b -> /a 会形成循环,此代码能跳过重复的 /a 或 /b。
示例: 假设目录结构如下:
/
├── a (符号链接 -> /b)
└── b
运行代码后:
- 遍历 /,发现 /a 是符号链接,解析为 /b。
- 如果 /b 未被访问过,继续遍历 /b。
- 遍历 /b 时,发现 /a 是符号链接,解析为 /b,发现 /b 已被访问过,跳过 /a 的子目录遍历。
注意事项:
- followlinks=True 可能会跟随恶意符号链接(如指向 / 的链接),需确保安全性。
- 如果不需要跟随符号链接,改用 followlinks=False(默认)。
- 此代码仅跳过重复目录,不处理文件或执行其他操作。
6. 遍历深度控制
限制递归深度为2层:
import os
max_depth = 2
for root, dirs, files in os.walk('.', topdown=True):
current_depth = root.count(os.sep)
if current_depth >= max_depth:
dirs[:] = [] # 清空子目录列表,停止深入
这段代码的作用是遍历当前目录(.)及其子目录,但限制遍历的最大深度为 2 层(即根目录 + 2 层子目录),超过深度的目录会被跳过。
代码解析:
- max_depth = 2
- 设置最大遍历深度为 2 层。
- for root, dirs, files in os.walk('.', topdown=True):
- 从当前目录(.)开始遍历。
- topdown=True 表明自顶向下遍历(默认),即先处理父目录,再处理子目录。
- root 是当前遍历的目录路径。
- dirs 是当前目录下的子目录列表(可原地修改)。
- files 是当前目录下的文件列表(未使用)。
- current_depth = root.count(os.sep)
- .(当前目录) → 0 层
- ./dir1 → 1 层
- ./dir1/dir2 → 2 层
- os.sep 是路径分隔符(如 Windows 为 ,Linux 为 /)。
- root.count(os.sep) 计算当前目录路径中的分隔符数量,从而估算深度。
- 例如:
- if current_depth >= max_depth:
- 如果当前深度 ≥ 最大深度(2),则清空 dirs 列表。
- dirs[:] = [] 原地清空子目录列表,使 os.walk 跳过该目录的子目录遍历。
示例:假设目录结构如下:
.
├── dir1
│ ├── file1.txt
│ └── dir2
│ └── file2.txt
└── dir3
└── file3.txt
运行代码后,输出(假设仅打印文件路径):
./dir1/file1.txt
./dir3/file3.txt
- ./dir1/dir2/file2.txt 不会被遍历,由于 ./dir1/dir2 是第 2 层,其子目录会被跳过。
适用场景:
- 限制目录遍历深度(如仅检查前两层目录)。
- 避免无限制递归(如处理超大目录树时节省资源)。
注意事项: - root.count(os.sep) 是一种简单估算深度的方式,但可能不准确(如路径中包含 .. 或无效分隔符)。
- 如果需要更准确的深度控制,可以改用栈或队列实现自定义遍历。
三、避坑指南与优化策略
1. 常见陷阱
- 路径拼接错误:
必须用os.path.join(root, file)而非字符串拼接(避免跨平台路径分隔符问题)。 - 循环引用风险:
启用followlinks=True时需用os.path.realpath检测循环。 - 修改dirnames影响遍历:
直接修改dirs[:](而非dirs)才能改变后续遍历行为。
2. 性能优化
- 目录剪枝:
提前过滤无关目录(如__pycache__):for root, dirs, files in os.walk('.'):
dirs[:] = [d for d in dirs if d != '__pycache__'] - 缓存文件属性:
使用@lru_cache缓存os.stat调用:from functools import lru_cache
@lru_cache(maxsize=3000)
def cached_stat(path):
return os.stat(path) - 并行处理:
结合ThreadPoolExecutor加速文件处理:from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor() as executor:
for root, _, files in os.walk('.'):
executor.map(process_file, [os.path.join(root, f) for f in files])
3. 异常处理
自定义错误回调函数:
def handle_error(error):
print(f"访问失败: {error.filename}", file=sys.stderr)
for root, dirs, files in os.walk('/', onerror=handle_error):
...
四、常见面试题及答案
- Q: os.walk与os.listdir的区别?
A: os.walk递归遍历所有子目录,返回三元组;os.listdir仅返回当前层内容。 - Q: 如何避免遍历隐藏文件?
A: 遍历时修改dirs[:]和files列表,过滤以.开头的项。 - Q: 遍历时如何动态跳过目录?
A: 在循环中删除dirs列表中的目录名(需原地修改dirs[:])。 - Q: 为何os.walk默认是深度优先(DFS)?
A: DFS优先处理子目录再回溯,符合文件树递归结构,且内存占用低。 - Q: 如何实现广度优先遍历(BFS)?
A: os.walk原生不支持BFS,需用队列手动实现。
总结
os.walk是Python目录遍历的工具之一,其价值在于递归生成能力和动态修改机制(通过dirnames控制遍历路径)。实际使用时需注意:
- 路径拼接必用os.path.join保障跨平台兼容性;
- 自底向上遍历(topdown=False)适合清理操作;
- 高频调用场景需结合缓存或并行计算优化I/O效率。
