当前位置:首页 > 文章列表 > 文章 > python教程 > Python pathlib.walk 如何在遍历时跳过目录树

Python pathlib.walk 如何在遍历时跳过目录树

来源:17golang原创 2026-10-09 23:22:56 0浏览 收藏

用 pathlib.Path.walk() 遍历目录树时,跳过整个子树的关键不是在拿到文件后再做过滤,而是在 top_down=True 模式下原地修改当前轮返回的 dirnames 列表。最常用的写法是 dirnames[:] = [...]:不想进入的目录名从列表中消失后,遍历器就不会继续下探对应目录。

这个方法适合排除 .git、__pycache__、node_modules、构建产物和按相对路径指定的私有目录。Path.walk() 从 Python 3.12 加入标准库;如果项目还在 Python 3.11 或更低版本,应继续使用行为相近的 os.walk()。

官方文档:https://docs.python.org/3/library/pathlib.html#pathlib.Path.walk

一、触发信号:过滤了文件,遍历却仍然进入大目录

常见故障信号是程序最终没有处理被排除目录中的文件,但扫描时间、磁盘读取量和权限告警一点没少。原因通常是过滤发生得太晚:代码先进入所有子目录,生成文件路径以后才用 if 丢掉结果。此时只是“不要这些文件”,并没有“不要访问这棵子树”。

Path.walk() 每轮产出 (dirpath, dirnames, filenames) 三元组。其中 dirpath 是当前目录的 Path 对象,另外两个列表中的元素只是名称字符串。要组成完整路径,需要写成 dirpath / name。官方文档还说明,这两个列表的顺序取决于文件系统;如果业务依赖稳定顺序,应显式排序。

from pathlib import Path

root = Path("project")

for dirpath, dirnames, filenames in root.walk():
    # dirnames 和 filenames 保存名称字符串,完整路径要与 dirpath 拼接。
    for filename in sorted(filenames):
        file_path = dirpath / filename
        print(file_path)

先确认运行环境:如果 Path(".").walk 不存在,通常意味着 Python 版本低于 3.12,而不是导入方式错误。这个版本边界应在部署检查中明确记录。

二、核心处理:只改 dirnames,而且要原地改

top_down 默认就是 True。在这个模式下,父目录的三元组会先交给调用方,遍历器随后根据同一个 dirnames 列表决定进入哪些子目录。因此必须修改列表本身,不能只把局部变量重新绑定到一个新列表。

Path根目录、dirnames原列表、忽略集合与剩余子目录的静态数据结构说明图
图1:top_down 模式下,过滤规则原地更新 dirnames;Path.walk 只继续访问列表中保留下来的子目录。
from pathlib import Path

SKIP_NAMES = {".git", "__pycache__", "node_modules", "dist", "build"}


def iter_source_files(root: Path):
    for dirpath, dirnames, filenames in root.walk(top_down=True):
        # 用切片赋值原地更新原列表,真正阻止遍历器进入这些子树。
        dirnames[:] = [name for name in dirnames if name not in SKIP_NAMES]

        for filename in filenames:
            path = dirpath / filename
            if path.suffix == ".py":
                yield path


for source_file in iter_source_files(Path("project")):
    print(source_file)

这里的 dirnames[:] = ... 与 dirnames = ... 看起来只差一个切片,效果却完全不同。前者保留原列表对象并替换内容,遍历器能看到变化;后者只是让当前函数里的名字指向新列表,遍历器仍持有旧列表,所以不会停止下探。

写法是否裁剪子树原因
dirnames[:] = kept是原列表内容被更新
dirnames.remove("dist")是直接修改原列表
dirnames = kept否只重新绑定局部变量
只过滤 filenames否不影响进入子目录的决定

三、按相对路径跳过,避免同名目录被全部排除

按名称过滤很直接,但它会排除任意层级中的同名目录。例如全局排除 cache,可能误伤源码树里一个合法的 src/cache 包。生产脚本通常需要两层规则:少量确定无用的目录按名称排除,位置敏感的目录按相对路径排除。

from pathlib import Path

SKIP_NAMES = {".git", "__pycache__"}
SKIP_RELATIVE = {
    Path("frontend/node_modules"),
    Path("services/report/private_exports"),
}


def should_descend(root: Path, parent: Path, name: str) -> bool:
    if name in SKIP_NAMES:
        return False

    child = parent / name
    # relative_to 让规则与根目录绑定,避免依赖机器上的绝对路径。
    relative_child = child.relative_to(root)
    return relative_child not in SKIP_RELATIVE


def collect_json_files(root: Path) -> list[Path]:
    found: list[Path] = []
    for dirpath, dirnames, filenames in root.walk(top_down=True):
        # 仍然原地更新,让相对路径规则参与下一层目录选择。
        dirnames[:] = [
            name for name in dirnames if should_descend(root, dirpath, name)
        ]
        found.extend(
            dirpath / name for name in filenames if name.endswith(".json")
        )
    return found

如果规则希望排除某个路径下的全部后代,不必把每一级子目录都列出来;只要在它作为父目录的 dirnames 项出现时将其删除即可。判断中优先使用 Path 运算,不要手写斜杠拼接字符串,这样 Windows 与 POSIX 路径分隔符差异不会进入规则。

四、把符号链接和读取错误放进边界检查

目录裁剪不是唯一边界。Path.walk() 默认 follow_symlinks=False,指向目录的符号链接会被放进 filenames,而不是当成普通子目录继续进入。这一点与 os.walk() 的分类行为存在差异,迁移旧代码时不能默认两者完全一致。

Path.walk 遍历方向、符号链接、错误回调与路径规则的静态依赖说明图
图2:目录裁剪还受遍历方向、符号链接策略、错误回调和相对路径规则共同约束。

如果显式开启 follow_symlinks=True,符号链接可能指回父目录,从而形成无限递归。官方实现不会自动记录已经访问过的目录,因此应保持默认值,或自行维护已访问目录集合。对扫描失败的目录,可通过 on_error 接收 OSError;回调返回后继续遍历,重新抛出则终止。

from pathlib import Path


def report_walk_error(error: OSError) -> None:
    # filename 会指出哪一个目录在扫描时失败,便于记录权限问题。
    failed_path = getattr(error, "filename", None)
    print(f"无法读取目录: {failed_path}; 原因: {error}")


def walk_safely(root: Path):
    for dirpath, dirnames, filenames in root.walk(
        top_down=True,
        on_error=report_walk_error,
        follow_symlinks=False,
    ):
        # 即使某个目录读取失败,其他可访问目录仍可继续处理。
        dirnames[:] = [name for name in dirnames if name != ".git"]
        yield dirpath, dirnames, filenames

还要注意遍历期间目录被替换的情况。官方文档说明,Path.walk() 假设目录树在遍历中不会被修改;如果 dirnames 里的目录后来被替换成符号链接,遍历行为可能不符合原先判断。面对持续变化的上传目录或发布目录,应使用快照目录、锁或版本化目录,而不是把一次遍历当成强一致视图。

五、快速验收:记录“访问过的目录”,不要只看最终文件

验收目录裁剪时,最有价值的信号是实际访问过哪些 dirpath。只检查最终文件列表,无法区分“没有进入被排除目录”和“进入后把文件结果丢掉”这两种实现。下面的最小测试构造一个临时目录树,并断言被排除子树没有出现在访问记录中。

from pathlib import Path
from tempfile import TemporaryDirectory


def visited_directories(root: Path) -> list[Path]:
    visited: list[Path] = []
    for dirpath, dirnames, _ in root.walk(top_down=True):
        # 先记录当前目录,再原地删除不允许继续访问的缓存目录。
        visited.append(dirpath.relative_to(root))
        dirnames[:] = [name for name in dirnames if name != "cache"]
    return visited


with TemporaryDirectory() as temporary:
    root = Path(temporary)
    (root / "src").mkdir()
    (root / "cache" / "deep").mkdir(parents=True)

    visited = visited_directories(root)
    # 被裁剪的 cache 及其 deep 子目录都不应出现在访问记录中。
    assert Path("cache") not in visited
    assert Path("cache/deep") not in visited
    assert Path("src") in visited

如果断言失败,按顺序检查三件事:是否使用 top_down=True;是否修改了 dirnames 而不是 filenames;是否使用切片赋值、remove() 或 del 修改原列表。top_down=False 时,即便修改 dirnames 也不会影响遍历,因为对应子目录在父目录三元组交给调用方之前就已经生成。

六、回滚与兼容:低版本改用 os.walk

如果发布后发现目标环境仍有 Python 3.11,最安全的回滚不是模拟一个不完整的 Path.walk(),而是切回 os.walk()。它同样支持在自顶向下模式中原地裁剪 dirs,只需把字符串根路径转换成 Path 后再进入后续业务逻辑。

import os
from pathlib import Path


def compatible_walk(root: Path):
    for root_text, dirnames, filenames in os.walk(root, topdown=True):
        # os.walk 也要求原地修改 dirnames 才能阻止进入子树。
        dirnames[:] = [name for name in dirnames if name != "node_modules"]
        yield Path(root_text), dirnames, filenames

回滚后要重新检查符号链接分类,因为 Path.walk() 在默认不跟随符号链接时,会把指向目录的链接列入 filenames。不要只替换函数名就认为语义完全一致。

发布前检查清单

  • 运行环境是 Python 3.12 或更高版本;否则采用 os.walk() 兼容实现。
  • 需要裁剪子树时保持 top_down=True。
  • 只通过切片赋值、remove() 或 del 原地修改 dirnames。
  • 名称规则与相对路径规则分开,避免误伤合法同名目录。
  • 默认不跟随符号链接;如必须开启,增加已访问目录防环机制。
  • 用 on_error 决定读取失败时继续还是终止,并记录 OSError.filename。
  • 验收时检查访问过的目录,而不只检查最终收集到的文件。

相关问题

为什么 dirnames = filtered 没有跳过目录?

因为它只让局部变量指向新列表,没有修改遍历器持有的原列表。应使用 dirnames[:] = filtered。

top_down=False 时还能裁剪目录吗?

不能通过修改当前轮 dirnames 来阻止下探,因为子目录已经先被生成。目录裁剪应使用自顶向下模式。

Path.walk 默认会进入目录符号链接吗?

默认不会。follow_symlinks=False 时,指向目录的符号链接会进入 filenames。开启跟随时要自行防止链接环。

可以直接排序 dirnames 吗?

可以。文件系统返回顺序不保证稳定,需要可重复顺序时可在原列表上调用 dirnames.sort(),同时仍可删除不需要进入的名称。

glob 或 rglob 能替代 walk 的子树裁剪吗?

它们适合按模式收集路径,但当需求是根据运行时规则阻止进入某棵子树时,walk() 提供的可修改 dirnames 更直接,也更容易验证访问边界。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
flight recorder 缓冲区太小会丢掉哪些事件flight recorder 缓冲区太小会丢掉哪些事件
上一篇
flight recorder 缓冲区太小会丢掉哪些事件
运行轨迹导出后时间线不完整通常是什么原因
下一篇
运行轨迹导出后时间线不完整通常是什么原因
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    395次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    475次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    480次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    426次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    251次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码