Python递归复制文件夹:shutil.copytree用法详解

追风的人
追风的人
发布时间:2026-11-09| 9人浏览过
原创

全面讲解Python标准库shutil.copytree函数的使用方法,涵盖基础递归复制、目标目录已存在合并处理、ignore排除规则、符号链接与元数据保留策略及常见错误排查。

主题:Python 标准库 shutil 模块 适用版本:Python 3.8+(含历史版本差异说明)
Python递归复制文件夹:shutil.copytree用法详解
图 0:shutil.copytree 递归复制机制概览与核心功能映射

在 Python 中批量复制文件夹时,普通的文件复制函数无法自动处理复杂的子目录嵌套结构。标准库内置的 shutil.copytree() 专用于解决这一问题,它能够以递归方式遍历源目录中的所有层级,同步创建对应的目录分支,并将内部的文件及元数据完整克隆到目标路径中。本文将深入拆解其核心参数、冲突覆盖规则、过滤匹配策略与典型避坑要点。

一、核心语法与基础用法(从一个最小示例开始)

shutil 是 Python 内置的高级文件操作模块,无需额外安装即可直接导入。其中,shutil.copytree() 的函数设计目标是“以源目录为模板,在目标位置完整重建该目录树”。

在深入各项进阶参数前,我们首先查看该函数在 Python 官方库中的标准签名:

shutil.copytree(
    src, 
    dst, 
    symlinks=False, 
    ignore=None, 
    copy_function=copy2, 
    ignore_dangling_symlinks=False, 
    dirs_exist_ok=False
)

完成一次基础递归复制仅需提供两个核心位置参数:src(源目录路径)与 dst(目标目录路径)。

import shutil

# 定义源目录与目标备份路径
source_dir = "/path/to/source_project"
backup_dir = "/path/to/backup_project"

# 执行递归复制
shutil.copytree(source_dir, backup_dir)
print("目录树复制完成!")

在执行上述最基础的调用时,函数内部会按照深度优先遍历逻辑运作:先在目标位置创建与源目录根同名(或自定义指定)的新文件夹,然后逐层向下探索。若遇到子目录,则自动调用 os.makedirs 创建对齐的路径;若遇到具体文件,则默认调用 shutil.copy2 将文件内容及其元数据(包括最后修改时间、访问时间、文件标记等)写入目标节点。

Python递归复制文件夹:shutil.copytree用法详解
图 1:shutil.copytree 递归复制时内部的遍历、建目录与文件克隆流程

二、目标目录已存在:dirs_exist_ok 参数的使用与行为差异

初学者使用 shutil.copytree() 最常遇到的报错是 FileExistsError: [Errno 17] File exists。这是因为在默认情况下,函数要求目标目录 dst 必须是一个尚未存在的路径。

1. Python 3.8 带来的重要更新

在 Python 3.8 之前,若目标目录已提前创建,copytree 会直接抛出异常中断执行。当时若要实现增量覆盖或合并,通常需要借助第三方库或手动递归编写 os.walk()。

自 Python 3.8 起,官方引入了 dirs_exist_ok 这一关键参数,默认值为 False。当显式将其指定为 True 时:

  • 如果目标目录(或内部子目录)已经存在,函数不会报错,而是允许目录复用,继续向深层执行。
  • 对于目标路径中已存在的同名文件,函数将直接采用新文件覆盖旧文件。
  • 目标路径中已有但源路径中没有的文件,会被原封不动地保留(即实现“合并/Merge”而非“清空后覆盖”)。
import shutil
from pathlib import Path

source = Path("./release_v2")
destination = Path("/var/www/html")

# 启用 dirs_exist_ok=True 实现平滑合并复制
shutil.copytree(source, destination, dirs_exist_ok=True)
print("静态站点文件已更新合并至目标服务目录。")
版本兼容性提醒

如果代码需要兼容 Python 3.7 及更早版本,切勿传入 dirs_exist_ok 参数,否则会抛出 TypeError: copytree() got an unexpected keyword argument 'dirs_exist_ok'。

Python递归复制文件夹:shutil.copytree用法详解
图 2:shutil.copytree 在不同参数设置下对目标已存在情况的处理流程

三、排除特定文件或目录:ignore 回调与 shutil.ignore_patterns

在实际项目备份或文件分发中,很多临时文件和版本控制元数据是不需要复制的,例如 .git 目录、Python 编译缓存 __pycache__、.DS_Store 或 *.log 文件。shutil.copytree() 提供了 ignore 参数来实现过滤。

1. 使用快捷匹配工具:shutil.ignore_patterns

最便捷的方式是使用 shutil.ignore_patterns(*patterns) 工厂函数。该函数支持类似 Unix shell 风格的通配符语法(例如 *、?、[seq])。

import shutil

# 复制整个项目目录,同时排除版本控制文件与字节码缓存
shutil.copytree(
    "my_project",
    "my_project_clean_backup",
    ignore=shutil.ignore_patterns(
        "*.pyc",
        "*.pyo",
        "__pycache__",
        ".git",
        "*.tmp",
        ".DS_Store"
    )
)

2. 自定义过滤函数的工作机制

为了理解 ignore_patterns 的工作原理,必须掌握 ignore 参数的签名要求。ignore 参数接收一个可调用对象(函数),其签名为:

def custom_ignore(dir_path, dir_contents):
    # dir_path: 当前正被访问的目录路径(字符串)
    # dir_contents: 该目录下所有直接子项的名称列表(即 os.listdir 返回的列表)
    # 返回值: 一个包含需要被“忽略(不复制)”的名称集合或列表
    return ignored_names

每当 copytree 进入一个新目录时,都会把当前目录的路径以及该目录下的所有子项名称传入此函数。函数计算出哪些名称属于被排除对象,copytree 便会在当前层直接跳过它们。如果被跳过的是一个文件夹,该文件夹及其内部的所有嵌套子项都将直接被剪枝,不再向下遍历。

Python递归复制文件夹:shutil.copytree用法详解
图 3:ignore 回调在每层目录扫描时的决策与分流处理

四、符号链接、文件元数据与底层复制行为定制

除了目录与文件过滤外,文件系统层面的软链接(symlink)和属性状态(stat)在备份和迁移过程中同样至关重要。

1. 符号链接处理:symlinks 参数

symlinks 参数控制是否将符号链接本身作为软链接复制,默认值为 False:

  • symlinks=False(默认):遇到软链接时,会追踪其指向的真实原始文件,将真实内容复制到目标位置(解除引用)。若真实文件不存在,会引发异常。
  • symlinks=True:在目标位置重新创建一个对等的软链接对象,而不解开链接。在支持软链接的类 Unix 系统中推荐在备份时开启,以避免重复复制大文件或破坏工程架构。

2. 悬空软链接:ignore_dangling_symlinks 参数

当 symlinks=False 时,如果源目录中包含一个指向已被删除文件的软链接(即悬空符号链接 / Dangling Symlink),copytree 会在尝试读取真实文件内容时抛出 FileNotFoundError。如果将 ignore_dangling_symlinks 设置为 True,函数将静默忽略该错误并跳过该项。

3. 元数据保留策略与 copy_function 参数

copytree 默认将 copy_function 设置为 shutil.copy2。与普通的 shutil.copy 相比,copy2 会在复制文件比特流后额外调用 shutil.copystat(),尽可能保留文件的:

  1. 最后访问时间(atime)与最后修改时间(mtime)。
  2. POSIX 权限模式(File Mode / Permissions)。
  3. 部分操作系统支持的拓展属性与安全标志。

若只需要复制内容而不需要保留修改时间(例如触发自动化构建重新编译),可以将 copy_function=shutil.copy 显式传入。

五、常见异常分析与高频避坑清单

在实际脚本化运行中,复制目录通常会跨越不同的磁盘卷或权限上下文,以下是高频发生的四类典型问题与排查指南:

异常名称 / 现象 常见诱发根因 标准排查与修复方案
FileExistsError 目标目录 dst 已经存在,且未开启覆盖允许。 在 Python 3.8+ 中指定 dirs_exist_ok=True;或提前清理已存在的目标目录。
无限嵌套递归 将目标目录放置在源目录内部(如 copytree('./data', './data/backup'))。 目标目录必须位于源目录树之外,或在 ignore 模式中显式排除 backup 文件夹名。
PermissionError 对源目录中某些文件缺少读权限,或对目标磁盘缺少写权限。 检查运行用户的读写权限;在 Windows 下注意文件是否被其他进程独占锁定。
shutil.Error 遍历过程中多个子文件复制报错,copytree 将多项异常聚合抛出。 捕获 shutil.Error 并遍历其 args[0] 列表,每项为一个三元组 (src, dst, err_msg)。
经典陷阱:源目录与目标目录的层级包含

如果在执行 shutil.copytree('/project', '/project/backup') 时没有在 ignore 中排除 backup,复制进程在扫描 /project 时会把刚创建的 backup 目录当作新发现的子目录继续向下钻取,导致递归深度激增或磁盘被写满。

Python递归复制文件夹:shutil.copytree用法详解
图 4:shutil.copytree 核心参数速查矩阵与高频异常应对方案

六、工业级完整实践范例

综合前文的所有知识点,下面给出一个健壮的工业级备份函数封装示例。该示例完整考虑了目标目录已存在、常见临时文件过滤、异常信息捕获以及符号链接配置:

import os
import shutil
from pathlib import Path

def robust_backup(src_path: str, dst_path: str):
    """
    健壮的目录树递归复制工具函数
    """
    src = Path(src_path).resolve()
    dst = Path(dst_path).resolve()

    if not src.exists() or not src.is_dir():
        raise ValueError(f"源路径不存在或并非目录: {src}")

    # 防止无限嵌套复制陷阱
    if dst == src or src in dst.parents:
        raise ValueError("目标目录不能位于源目录内部,避免无限循环递归!")

    print(f"开始备份: {src} -> {dst}")

    try:
        shutil.copytree(
            src,
            dst,
            symlinks=True,  # 类 Unix 环境下推荐保留软链接本身
            ignore=shutil.ignore_patterns(
                "*.pyc",
                "__pycache__",
                ".git",
                ".DS_Store",
                "*.tmp"
            ),
            dirs_exist_ok=True  # 允许目标存在并进行平滑合并覆盖
        )
        print("目录树复制成功完成!")

    except shutil.Error as err:
        print("复制过程中部分文件发生错误:")
        for src_item, dst_item, msg in err.args[0]:
            print(f"  文件: {src_item} -> {dst_item},原因: {msg}")
    except PermissionError as err:
        print(f"权限不足,无法完成文件读写: {err}")
    except Exception as err:
        print(f"复制失败,未知错误: {err}")

if __name__ == "__main__":
    # 执行备份测试
    robust_backup("./source_data", "./backup_data")

总结来说,shutil.copytree 是 Python 中处理复杂目录结构克隆与分发最直接、高效的原生工具。通过掌握 dirs_exist_ok=True 的合并特性,以及结合 shutil.ignore_patterns 的剪枝过滤能力,读者可以在自动化运维、版本打包、数据备份等各类日常开发场景中游刃有余地完成目录复制任务。

相关专题:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。