Python os.walk函数递归遍历目录

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)及其所有子目录,并打印出每个文件的完整路径。

具体来说:

  1. os.walk('example_folder') 会生成一个三元组 (root, dirs, files),其中:
  2. root 是当前遍历的目录路径。
  3. dirs 是当前目录下的子目录列表。
  4. files 是当前目录下的文件列表。
  5. 外层循环 for root, dirs, files in os.walk('example_folder') 遍历每个目录及其子目录。
  6. 内层循环 for file in files 遍历当前目录下的每个文件。
  7. os.path.join(root, file) 将目录路径和文件名拼接成文件的完整路径。
  8. 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 文件数量。

具体分析:

  1. os.walk('/project') 生成一个三元组 (root, dirs, files),其中:
  2. root 是当前遍历的目录路径。
  3. dirs 是当前目录下的子目录列表(这里用 _ 忽略,由于不需要)。
  4. files 是当前目录下的文件列表。
  5. 外层循环 for root, _, files in os.walk('/project') 遍历 /project 目录及其所有子目录。
  6. 内层循环 for file in files 遍历当前目录下的每个文件。
  7. if file.endswith('.py') 检查文件名是否以 .py 结尾(即是否是 Python 文件)。
  8. py_files.append(os.path.join(root, file)) 将符合条件的 Python 文件的完整路径添加到 py_files 列表中。
  9. 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))

这段代码的作用是遍历当前目录(.)及其所有子目录,并打印出每个文件的完整路径,同时跳过所有隐藏目录(即名称以 . 开头的目录)。

具体分析:

  1. os.walk('.') 生成一个三元组 (root, dirs, files),其中:
  2. root 是当前遍历的目录路径。
  3. dirs 是当前目录下的子目录列表。
  4. files 是当前目录下的文件列表。
  5. dirs[:] = [d for d in dirs if not d.startswith('.')] 是一个原地修改操作,它会过滤掉 dirs 列表中所有以 . 开头的目录(即隐藏目录),从而在后续的遍历中跳过这些目录。
  6. dirs[:] 表明对 dirs 列表进行原地修改(而不是创建新列表)。
  7. [d for d in dirs if not d.startswith('.')] 是一个列表推导式,筛选出不以 . 开头的目录。
  8. for file in files 遍历当前目录下的每个文件。
  9. 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),确保先删除文件和子目录,再删除父目录。

代码解析:

  1. os.walk('.', topdown=False)
  2. . 表明当前目录。
  3. topdown=False 表明自底向上遍历(先子目录,后父目录),这是删除操作的关键,由于需要先删除子目录中的文件和目录,再删除父目录。
  4. for file in files:
  5. 遍历当前目录下的所有文件。
  6. os.remove(os.path.join(root, file)) 删除当前文件。
  7. if not os.listdir(root):
  8. 检查当前目录是否为空(os.listdir(root) 返回目录内容,如果为空则返回空列表)。
  9. 如果为空,则执行 os.rmdir(root) 删除该目录。

注意事项

  1. 危险操作!
  2. 这段代码会永久删除当前目录及其所有内容,请谨慎使用!
  3. 提议先备份重大数据,或用 print 取代 os.remove 测试输出路径。
  4. 权限问题
  5. 如果目录或文件被占用(如被其他程序使用),删除会失败并抛出异常(如 PermissionError)。
  6. 符号链接(Symlinks)
  7. 默认情况下,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() 检查并跳过重复的目录(如符号链接指向的同一目录)。这样可以避免无限循环或重复处理符号链接导致的重复目录。

代码解析:

  1. visited = set()
  2. 初始化一个集合,用于存储已访问的目录的真实路径(解析符号链接后的路径)。
  3. for root, dirs, files in os.walk('/', followlinks=True):
  4. os.walk('/') 从根目录开始遍历。
  5. followlinks=True 允许跟随符号链接(否则会跳过符号链接指向的目录)。
  6. root 是当前遍历的目录路径(可能是符号链接)。
  7. dirs 是当前目录下的子目录列表(会被原地修改)。
  8. files 是当前目录下的文件列表(未使用)。
  9. real_path = os.path.realpath(root)
  10. 解析 root 的真实路径(如 /a -> /b,则 real_path 为 /b)。
  11. 这样能检测到符号链接指向的同一目录。
  12. if real_path in visited:
  13. 如果 real_path 已被访问过,说明当前目录是重复的(可能是符号链接)。
  14. dirs[:] = [] 清空 dirs 列表,使 os.walk 跳过该目录的子目录遍历。
  15. else: visited.add(real_path)
  16. 如果 real_path 未被访问过,将其加入 visited 集合,并继续正常遍历。

适用场景

  • 遍历包含符号链接的目录结构时,避免重复处理同一目录。
  • 例如,/a -> /b 和 /b -> /a 会形成循环,此代码能跳过重复的 /a 或 /b。

示例: 假设目录结构如下:

/
├── a (符号链接 -> /b)
└── b

运行代码后:

  1. 遍历 /,发现 /a 是符号链接,解析为 /b。
  2. 如果 /b 未被访问过,继续遍历 /b。
  3. 遍历 /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 层子目录),超过深度的目录会被跳过。

代码解析:

  1. max_depth = 2
  2. 设置最大遍历深度为 2 层。
  3. for root, dirs, files in os.walk('.', topdown=True):
  4. 从当前目录(.)开始遍历。
  5. topdown=True 表明自顶向下遍历(默认),即先处理父目录,再处理子目录。
  6. root 是当前遍历的目录路径。
  7. dirs 是当前目录下的子目录列表(可原地修改)。
  8. files 是当前目录下的文件列表(未使用)。
  9. current_depth = root.count(os.sep)
  10. .(当前目录) → 0 层
  11. ./dir1 → 1 层
  12. ./dir1/dir2 → 2 层
  13. os.sep 是路径分隔符(如 Windows 为 ,Linux 为 /)。
  14. root.count(os.sep) 计算当前目录路径中的分隔符数量,从而估算深度。
  15. 例如:
  16. if current_depth >= max_depth:
  17. 如果当前深度 ≥ 最大深度(2),则清空 dirs 列表。
  18. 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. 性能优化

  1. 目录剪枝
    提前过滤无关目录(如__pycache__):for root, dirs, files in os.walk('.'):
    dirs[:] = [d for d in dirs if d != '__pycache__']
  2. 缓存文件属性
    使用@lru_cache缓存os.stat调用:from functools import lru_cache
    @lru_cache(maxsize=3000)
    def cached_stat(path):
    return os.stat(path)
  3. 并行处理
    结合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):
    ...

四、常见面试题及答案

  1. Q: os.walk与os.listdir的区别?
    A: os.walk递归遍历所有子目录,返回三元组;os.listdir仅返回当前层内容。
  2. Q: 如何避免遍历隐藏文件?
    A: 遍历时修改dirs[:]和files列表,过滤以.开头的项。
  3. Q: 遍历时如何动态跳过目录?
    A: 在循环中删除dirs列表中的目录名(需原地修改dirs[:])。
  4. Q: 为何os.walk默认是深度优先(DFS)?
    A: DFS优先处理子目录再回溯,符合文件树递归结构,且内存占用低。
  5. Q: 如何实现广度优先遍历(BFS)?
    A: os.walk原生不支持BFS,需用队列手动实现。

总结

os.walk是Python目录遍历的工具之一,其价值在于递归生成能力动态修改机制(通过dirnames控制遍历路径)。实际使用时需注意:

  1. 路径拼接必用os.path.join保障跨平台兼容性;
  2. 自底向上遍历(topdown=False)适合清理操作;
  3. 高频调用场景需结合缓存或并行计算优化I/O效率。
© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...