Python批量获取目录下所有文件路径的方法

陆景行
陆景行
发布时间:2026-10-31 | 5人浏览过
原创

深入讲解 Python 批量获取指定目录下所有文件路径的四大主流方法,涵盖 pathlib.Path.rglob()、os.walk()、os.scandir() 和 glob.glob() 的语法示例、底层性能机制、目录剪枝与选型指南。

获取指定目录下的所有文件路径是自动化脚本、数据处理与工程批处理中最基础的操作。在 Python 中,核心解决路径通常分为两类:日常开发与代码维护优先使用面向对象的 pathlib.Path.rglob(),兼顾可读性与跨平台规范;而在需要跳过指定子目录(剪枝)或处理数万乃至百万级超大目录的高吞吐场景下,则分别采用经典递归工具 os.walk() 与低层缓存加速的 os.scandir()。
Python批量获取目录下所有文件路径的方法
图 0:目录树扫描与文件路径提取全局架构概览

一、核心方法对比与技术架构

在标准库中,Python 提供了多个用于文件和目录遍历的模块。很多开发者在初学阶段容易混淆 os.listdir()、glob.glob()、os.walk() 以及 pathlib 之间的边界。理解各模块的底层机制有助于在实际项目中做出精准的技术选型。

Python批量获取目录下所有文件路径的方法
图 1:四种文件路径获取方案技术架构与特性矩阵

从架构上看,各方案的区别体现在三处关键设计上:

  • 内存加载机制:glob.glob() 默认返回完整的列表,一次性将所有路径载入内存;而 pathlib.Path.rglob()、os.walk() 以及 os.scandir() 返回迭代器或生成器,具备惰性求值特性,内存开销恒定。
  • 文件属性查询:传统的 os.listdir() 结合 os.path.isfile() 会针对每一个文件名触发一次底层的 stat() 系统调用;而现代的 os.scandir() 和基于其封装的模块会在目录读取时预先缓存文件类型信息,避免重复上下文切换。
  • 路径抽象类型:os 与 glob 返回纯字符串路径,需要手动处理不同操作系统的分隔符;pathlib 则提供面向对象的 Path 实例,内置统一的路径拼接与解析接口。

二、方案一:现代化工程首选 pathlib.Path.rglob()

自 Python 3.4 引入并在后续版本不断优化的 pathlib 模块,是目前编写现代 Python 代码的首选。其中 rglob() 方法用于递归匹配目录树下的所有文件。

Python批量获取目录下所有文件路径的方法
图 2:基于 pathlib 的目录扫描与路径提取执行流水线

1. 完整代码示例

from pathlib import Path
from typing import List

def get_all_file_paths_pathlib(dir_path: str, ext: str = None) -> List[str]:
    """
    使用 pathlib 递归获取指定目录下所有文件的绝对路径
    :param dir_path: 目标目录路径
    :param ext: 可选,按文件扩展名过滤(例如 '.py' 或 '.csv')
    :return: 规范化的绝对路径字符串列表
    """
    target_dir = Path(dir_path)
    
    # 基础校验:确保目标路径存在且为目录
    if not target_dir.exists() or not target_dir.is_dir():
        raise ValueError(f"指定的路径不存在或不是有效目录: {dir_path}")

    file_paths = []
    # rglob('*') 匹配当前目录及其所有子目录中的所有节点
    for item in target_dir.rglob('*'):
        # 必须显式判断 item.is_file(),因为 rglob 同时会产出子目录本身的 Path 对象
        if item.is_file():
            if ext is None or item.suffix.lower() == ext.lower():
                # resolve() 解析软链接与相对标记,返回标准化绝对路径
                file_paths.append(str(item.resolve()))

    return file_paths

if __name__ == '__main__':
    # 示例调用
    paths = get_all_file_paths_pathlib('./sample_project', ext='.py')
    print(f"找到符合条件的文件总数: {len(paths)}")
    for p in paths[:3]:
        print(f" - {p}")
控制台运行示例输出 找到符合条件的文件总数: 5
- /Users/developer/sample_project/main.py
- /Users/developer/sample_project/utils/helper.py
- /Users/developer/sample_project/modules/core.py
注意事项:目录过滤与性能控制
rglob('*') 产出的结果既包含文件也包含中间目录。如果不调用 item.is_file() 进行校验,获取到的列表中将混杂子目录自身的路径。此外,若只需遍历直接子级目录而不需要深层递归,将 rglob('*') 替换为 glob('*') 即可。

三、方案二:目录剪枝与深度遍历 os.walk()

当目标文件夹中包含版本控制目录(如 .git)、依赖缓存(如 node_modules、__pycache__)或无权限访问的特定敏感目录时,os.walk() 是实现高效排除(剪枝)的标准工具。

os.walk() 在每轮迭代中产生一个包含三个元素的三元组:(root, dirs, files)。关键机制在于:在循环体内直接就地修改 dirs 列表的内容,os.walk() 将严格放弃遍历被移除的目录子树。

Python批量获取目录下所有文件路径的方法
图 3:os.walk() 就地剪枝机制与 os.scandir() 元数据缓存原理

1. 支持目录排除的剪枝代码

import os
from typing import List, Set

def get_files_with_pruning(base_dir: str, exclude_dirs: Set[str] = None) -> List[str]:
    """
    使用 os.walk 遍历目录,支持就地剪枝排除指定子目录
    :param base_dir: 根目录路径
    :param exclude_dirs: 需要跳过的目录名集合(例如 {'.git', 'node_modules', '__pycache__'})
    :return: 文件的绝对路径列表
    """
    if exclude_dirs is None:
        exclude_dirs = {'.git', '.idea', 'node_modules', '__pycache__', '.venv'}

    result = []
    
    # topdown=True 是能够进行目录剪枝的必要前提
    for root, dirs, files in os.walk(base_dir, topdown=True):
        # 核心剪枝操作:使用切片赋值就地修改 dirs 列表
        # dirs[:] 改变的是原列表内存引用,不可使用 dirs = [...] 替换
        dirs[:] = [d for d in dirs if d not in exclude_dirs]
        
        for file_name in files:
            full_path = os.path.join(root, file_name)
            result.append(os.path.abspath(full_path))

    return result

if __name__ == '__main__':
    # 示例调用
    matched_files = get_files_with_pruning('/var/www/my_project')
    print(f"剪枝过滤后收集到的文件数量: {len(matched_files)}")
控制台运行示例输出 剪枝过滤后收集到的文件数量: 142
重点排错提示:为什么普通赋值不能剪枝?
若在循环中写成 dirs = [d for d in dirs if ...],只是让局部变量 dirs 指向了新列表对象,os.walk() 内部持有的原有列表并未改变,遍历器依旧会深入被排除的目录。必须使用 dirs[:] = [...] 进行就地切片替换。

四、方案三:极致吞吐与系统级调用优化 os.scandir()

在面对包含数十万乃至数百万个文件的大型存储、日志归档或文件服务器时,常规遍历往往消耗大量 CPU 时间在用户态与内核态的切换上。Python 3.5 引入的 os.scandir() 返回轻量级的 os.DirEntry 对象,直接复用底层操作系统 readdir 系统调用返回的元数据缓存。

1. 递归生成器实现

os.scandir() 本身是非递归的。结合生成器函数,可以构建一个占用内存极小且 I/O 效率极高的全目录扫描工具:

import os
from typing import Iterator

def scan_files_fast(directory: str) -> Iterator[str]:
    """
    基于 os.scandir 实现的高性能流式文件扫描生成器
    :param directory: 目标根目录
    :return: 文件的绝对路径生成器
    """
    try:
        # scandir 作为上下文管理器可以确保及时释放底层系统文件描述符
        with os.scandir(directory) as entries:
            for entry in entries:
                try:
                    # entry.is_file() 优先从 dirent 缓存中读取,不触发附加 stat 系统调用
                    if entry.is_file(follow_symlinks=False):
                        yield entry.path
                    elif entry.is_dir(follow_symlinks=False):
                        # 发现子目录时递归调用自身
                        yield from scan_files_fast(entry.path)
                except PermissionError:
                    # 单个文件访问权限受限时优雅跳过,保证主流程不中断
                    continue
    except PermissionError:
        # 针对无权限访问的子目录进行静默跳过
        pass

if __name__ == '__main__':
    # 流式消费示例:无需一次性生成大列表,边扫描边处理
    count = 0
    for file_path in scan_files_fast('/mnt/storage/dataset'):
        count += 1
        if count <= 2:
            print(f"流式提取: {file_path}")
    print(f"扫描完毕,累计处理文件: {count}")
控制台运行示例输出 流式提取: /mnt/storage/dataset/batch_01/img_001.png
流式提取: /mnt/storage/dataset/batch_01/img_002.png
扫描完毕,累计处理文件: 350280

五、方案四:通配符模式匹配 glob.glob()

glob 模块支持类 Unix Shell 风格的文件名模式匹配(如 * 匹配任意字符,? 匹配单个字符,[0-9] 匹配数字区间)。如果只需要编写几行自动化脚本来提取特定格式的文件,glob 是最简短的方式。

1. 递归提取示例

import glob
import os

def get_files_by_glob(search_pattern: str) -> list:
    """
    使用 glob 模块按规则提取文件路径
    :param search_pattern: 匹配表达式,必须包含 ** 并启用 recursive=True 才能跨目录递归
    """
    # recursive=True 允许 ** 匹配任意深度的层级目录
    return glob.glob(search_pattern, recursive=True)

if __name__ == '__main__':
    # 示例:递归查找当前目录下所有子目录中的所有 json 文件
    pattern = "./data/**/*.json"
    results = get_files_by_glob(pattern)
    
    # 转换为绝对路径
    abs_results = [os.path.abspath(p) for p in results if os.path.isfile(p)]
    print(f"匹配到的 JSON 文件数目: {len(abs_results)}")
控制台运行示例输出 匹配到的 JSON 文件数目: 18

注意:当目标目录层级极深时,glob.glob() 会在内存中一次性分配全部匹配项的列表。若数据量庞大,建议改用 glob.iglob() 获得惰性生成器。

六、工程实践中的边界陷阱与防范

在实际生产部署中,批量获取文件路径常常面临跨平台差异、权限拦截和死循环等现实问题:

  1. 符号链接导致的死循环(Symlink Recursion):如果目录下存在指向父级目录或互相关联的软链接,递归遍历可能陷入无限死循环并触发栈溢出。在 pathlib 与 os.scandir 中,默认行为均不会跟随危险的软链接(follow_symlinks=False)。在自定义递归中应保持此默认值,或通过检查 os.path.realpath() 记录已访问的 inode。
  2. Windows 与 POSIX 分隔符兼容性:在 Windows 系统中路径分隔符为反斜杠 \,而在 Linux/macOS 中为正斜杠 /。直接使用字符串拼接(例如 root + '/' + file)极易引发错误。应始终使用 os.path.join() 或 pathlib.Path / "sub_path" 语法。
  3. 无权限目录与 PermissionError:大型服务器或多用户环境下经常会遇到无读权限的文件夹。如果不加防护,单个无权子目录将导致整个扫描任务崩溃。编写递归逻辑时需在访问各级目录时加入 try...except PermissionError 保护。

七、全方案对比与选型决策指南

针对不同的业务规模和功能需求,可参考以下特性对比矩阵与决策树进行选型:

方法名称 所属模块 返回形态 深层递归 就地剪枝 I/O 吞吐级别 推荐使用场景
Path.rglob() pathlib 生成器 (Path 对象) 原生支持 需手动过滤 良好 (标准级) 标准首选 日常业务逻辑与跨平台通用开发
os.walk() os 迭代器 (三元组) 原生支持 原生支持 (修改 dirs) 良好 (标准级) 需要灵活排除指定子目录、忽略隐藏版本库
os.scandir() os 迭代器 (DirEntry) 需编写递归 支持 极致极速 (缓存元数据) 十万至百万级以上超大规模文件的批量批处理
glob.glob() glob 列表 / 生成器 (iglob) 需结合 ** 参数 不支持 中等 运维脚本、临时任务中基于后缀的一行代码快速抓取
Python批量获取目录下所有文件路径的方法
图 4:Python 批量获取文件路径方法选型决策树

总结:对于绝大多数常规开发任务,直接使用 pathlib.Path.rglob() 能够以最小的代码心智负担获得最佳的类型安全与代码可读性;如果遇到需要跳过 .git 等无用子树的场景,采用 os.walk() 配合 dirs[:] 剪枝是性价比最高的方式;而当性能瓶颈出现在极高密度的海量 I/O 操作时,采用基于 os.scandir() 构建的自定义递归生成器将释放最大吞吐潜力。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表