登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python Path.iterdir 遇到权限错误时怎么继续扫描

来源:17golang原创

时间:2026-09-07 23:36:49 468浏览 收藏

用 Path.iterdir() 扫描目录时,最稳妥的处理不是在整棵目录树外面包一个大 try,而是把异常边界放在“当前目录”这一层:当前目录没有读取权限就记录下来,继续处理其他目录;拿到子路径后,再分别判断它是目录还是普通文件。

关键点是把“无法读取目录”和“路径不是目录”分开。iterdir() 访问不可用目录时会抛出 OSError(常见表现是 PermissionError),而 is_dir()、is_file() 可能把不可访问路径返回为 False。所以结果里要保留 skipped_paths,不能把它们悄悄算成普通文件。

先记住三个结果: files 是确认过的文件,visited_dirs 是进入过的目录,skipped_paths 是本轮没有权限或检查失败的路径。

先把扫描范围和相对路径固定下来

问题现场通常从一个相对目录开始,例如用户传入 data/incoming。如果程序中途切换工作目录,后续拼接出来的路径就可能指向另一个位置。入口处只做一次解析:先用 Path 表达路径,再用 resolve() 得到明确根目录。子路径用斜杠运算符拼接,不要把字符串和分隔符混在一起。

from pathlib import Path

def scan_tree(root_text: str):
    # 只在入口确定一次根目录,避免相对路径随工作目录变化
    root = Path(root_text).expanduser().resolve()
    return root

root = scan_tree("data/incoming")
child = root / "2026" / "09"
print(root)
print(child)

这里的 resolve() 解决的是“从哪里扫”的问题,不代表目录一定可读。路径解析成功后,仍然要在真正调用 iterdir() 的位置处理权限。

Path.resolve、相对路径、Path.iterdir 和 PermissionError 的静态边界关系图
图1:把路径解析、相对路径拼接和目录读取异常放在不同边界内,扫描范围才不会失控。

在每个 Path.iterdir() 边界捕获权限异常

Path.iterdir() 返回的是迭代器,异常可能在开始取值时出现。因此要在遍历当前目录的局部位置捕获 OSError,而不是只捕获创建迭代器的那一行。权限不足时,把当前目录写入 skipped_paths,然后返回空列表,外层队列仍可继续。

def safe_children(directory: Path, skipped_paths: list[Path]):
    # 目录不可访问只影响当前节点,不终止整棵树
    try:
        return list(directory.iterdir())
    except OSError as exc:
        skipped_paths.append(directory)
        print(f"跳过 {directory}: {exc}")  # 保留可追踪的原因
        return []

捕获范围建议使用 OSError,因为文件系统错误不只有 PermissionError,还可能有目录消失、挂载点短暂不可用等情况。如果业务必须区分权限拒绝,再对 PermissionError 单独计数。

用 Path.is_dir() 和 Path.is_file() 分开判断类型

拿到 child 后不要靠后缀判断目录。目录继续进入待扫描队列,普通文件才加入 files。类型判断也要有异常边界:在网络盘或目录被并发删除时,判断结果可能已经失效;这类路径应进入跳过清单,避免报告看起来“扫描完成”但实际漏掉对象。

def scan_tree(root: Path):
    pending = [root]
    files, visited_dirs, skipped_paths = [], [], []

    while pending:
        # 从队列取一个目录;列表只保存 Path,不保存不稳定的字符串
        directory = pending.pop()
        visited_dirs.append(directory)
        for child in safe_children(directory, skipped_paths):
            try:
                if child.is_dir():
                    pending.append(child)
                elif child.is_file():
                    files.append(child)
            except OSError as exc:
                skipped_paths.append(child)
                print(f"无法判断 {child}: {exc}")

    return files, visited_dirs, skipped_paths

这段代码的边界很清楚:iterdir() 负责拿到当前目录的孩子,is_dir() 和 is_file() 负责分类。不要把 False 直接改写成“文件”,因为不可访问和非文件是两件事。

Path.iterdir 子路径经过目录判断和文件判断后进入结果清单的静态关系图
图2:同一个 child path 经过目录判断和文件判断后,分别归入继续扫描、文件结果或跳过记录。

用结果清单确认哪些路径被跳过

调用方最终至少要拿到三类结果,而不是只返回一个文件列表。这样可以把“没有文件”和“没有权限看到文件”区分开。

结果含义建议动作
files已确认是普通文件继续读取或交给后续处理
visited_dirs程序尝试进入的目录用于统计扫描范围
skipped_paths权限、消失或类型判断失败告警、重试或交给管理员处理

如果 skipped_paths 非空,报告应明确写出“部分路径未扫描”,而不要输出“扫描完成”这种容易误解的结论。需要完整读取时,再从权限、挂载状态或运行账号入手修复环境;代码本身只负责可控地跳过并留下证据。

Path.glob() 能不能直接替代?

如果只需要匹配文件名,glob() 或 rglob() 更省代码;但它们会抑制扫描阶段的部分 OSError,不适合要求列出每个跳过目录的审计型任务。

为什么不用 os.walk()?

os.walk() 也能配合错误回调继续遍历。本文保留 Path 链路,是为了让路径拼接、类型判断和结果记录保持同一种对象模型;如果项目已经围绕文件描述符或大量目录做性能优化,再考虑切换实现。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>