应传入dirs_exist_ok=True(Python 3.8+)并配合copy_function=shutil.copy2及ignore参数处理只读文件与过滤目录;旧版本需先shutil.rmtree(dst)再copytree。

shutil.copytree() 复制整个文件夹时 PermissionError 怎么办?
直接调用 shutil.copytree() 会失败,如果目标路径已存在,它会直接抛出 FileExistsError;如果源目录里有只读文件(比如 .git/ 中的文件),又可能触发 PermissionError。这不是 bug,是设计使然——它默认不做覆盖、不绕过权限检查。
解决办法是传入 dirs_exist_ok=True(Python 3.8+)并配合自定义 ignore 或 copy_function:
import shutil
import os
<p>def copy_with_permissions(src, dst):
shutil.copytree(
src, dst,
dirs_exist_ok=True,
ignore=shutil.ignore_patterns('<strong>pycache</strong>', '.git'),
copy_function=shutil.copy2 # 保留 mtime/atime
)</p><h1>若需兼容旧版 Python(<3.8),先手动删除 dst 再 copytree</h1><p>if os.path.exists(dst):
shutil.rmtree(dst)
shutil.copytree(src, dst)shutil.move() 移动文件夹跨磁盘时实际是“复制 + 删除”,怎么确认是否成功?
shutil.move() 在同一文件系统内是原子重命名(快且安全),但跨分区(比如从 /home 到 /mnt/usb)会退化为 shutil.copytree() + shutil.rmtree()。这意味着:中途失败会导致源还在、目标不完整,极易误判“移动成功”。
必须检查返回值和异常,并验证目标完整性:
立即学习“Python免费学习笔记(深入)”;
- 捕获
OSError和shutil.Error,它们都可能在复制阶段抛出 - 移动后用
os.path.exists(dst)+shutil.disk_usage(dst)粗略校验大小 - 关键场景建议改用
shutil.copytree()+ 手动shutil.rmtree(src),把两步拆开,失败时可回滚
如何跳过特定子目录(如 __pycache__、.venv)而不写死 ignore 列表?
shutil.copytree() 的 ignore 参数接受函数,比 ignore_patterns() 更灵活。例如动态排除所有以 . 或 __ 开头的目录:
def ignore_hidden_or_cache(path, names):
ignored = []
for name in names:
full_path = os.path.join(path, name)
if name.startswith('.') or name.startswith('__') or name == 'venv':
ignored.append(name)
elif os.path.isdir(full_path) and name.lower() in ['env', 'node_modules']:
ignored.append(name)
return ignored
<p>shutil.copytree(src, dst, ignore=ignore_hidden_or_cache)注意:这个函数接收的是父路径 path 和当前层的 names 列表,不是单个文件名;返回要忽略的名称列表,不是布尔值。
shutil.copytree() 和 os.walk() + 手动复制,哪个更适合大文件夹?
对含数万小文件的目录,shutil.copytree() 更稳——它内部做了缓冲和错误聚合;而自己用 os.walk() 循环调用 shutil.copy2() 容易因某次失败中断,且没内置符号链接/权限处理逻辑。
但如果需要精细控制(比如按文件类型走不同复制逻辑、跳过超大文件、记录进度),就得自己遍历:
- 用
os.walk(top, follow_symlinks=False)避免循环软链 - 对每个
file调用shutil.copy2(file, dst_path)(保留时间戳) - 对每个
dir先os.makedirs(dst_dir, exist_ok=True) - 别忘了显式处理
os.symlink()(shutil.copytree()默认不复制符号链接内容)
真正难的不是代码量,而是边界情况:硬链接、设备文件、挂载点、ACL 权限——这些 shutil 默认不碰,自己实现时得提前想清楚要不要支持。


















