shutil.copyfile()仅复制文件内容,不保留元数据;shutil.copy()在此基础上调用copystat()复制权限和时间戳,适用于需保持属性的备份场景。

shutil.copyfile() 和 shutil.copy() 有什么区别?
shutil.copyfile() 只复制文件内容,不保留元数据(如权限、修改时间),也不支持目录;shutil.copy() 在 copyfile() 基础上额外调用 copystat(),把 st_mode、st_atime、st_mtime 等也复制过去。对大文件来说,多一次 stat() + chmod() + utime() 调用,开销可感知,但通常不是瓶颈。
- 如果只要内容一致,用
shutil.copyfile()略快且语义更清晰 - 如果需保持权限或时间戳(比如备份场景),必须用
shutil.copy()或shutil.copy2() -
shutil.copy2()和shutil.copy()在 Python 3.10 中行为一致(都调用copystat()),不用刻意选copy2
为什么大文件复制慢?和缓冲区大小有关吗?
Python 3.10 的 shutil.copyfile() 默认使用 64 KiB 缓冲区(shutil._COPY_BUFSIZE),在大多数 Linux/Windows 上已足够。盲目调大缓冲区(比如设成 1 MiB)不一定更快——它受限于底层 read()/write() 系统调用效率、磁盘 I/O 调度策略,甚至 Python 的 GIL 在单线程复制中也会轻微影响吞吐。
- 复制本地 SSD 到 SSD:64 KiB ~ 1 MiB 区间性能差异通常 <5%
- 复制到机械盘或网络挂载盘(如 NFS/CIFS):小缓冲区反而更稳,避免阻塞等待
- 真正的瓶颈常是磁盘带宽或目标盘随机写入延迟,不是 Python 层缓冲
如果真要调优,可临时覆盖缓冲区:
import shutil
shutil._COPY_BUFSIZE = 1024 * 1024 # 1 MiB
shutil.copyfile("src.bin", "dst.bin")
但建议先用 time dd if=/dev/zero of=test.bin bs=1M count=1024 测下磁盘原生写速,再决定是否值得折腾。
立即学习“Python免费学习笔记(深入)”;
能否跳过校验直接复制?
shutil.copyfile() 不做校验,就是裸字节流复制,这点很干净。但要注意:
- 它不检查源文件是否被其他进程截断或修改(复制中途变更会得到损坏副本)
- 它不处理“磁盘满”错误的细粒度恢复(失败时目标文件可能已部分写入,需手动清理)
- 它不支持断点续传,网络不稳定时不适合
如果你需要可靠性,别自己加 MD5 校验(那会读两遍,加倍耗时),而是用外部工具或改用 rsync --partial --progress 驱动 Python 子进程:
import subprocess subprocess.run(["rsync", "-W", "src.bin", "dst.bin"])
-W 强制整块传输,避免 rsync 自己的分块校验开销,效果接近 shutil.copyfile() 但带原子性和失败保护。
Python 3.10 里还有没有更快的替代方案?
有,但要看场景:
- 如果目标是同一文件系统内的“快速复制”,
os.link()(硬链接)或os.symlink()(软链接)几乎零耗时,但仅限同分区且不要求独立文件 - 如果支持
copy_file_range系统调用(Linux 4.5+),可用shutil.copyfileobj()配合os.sendfile()手动实现零拷贝,绕过用户态内存拷贝 -
shutil.copyfile()在 Python 3.10 已默认尝试copy_file_range(见shutil._fastcopy_sendfile),但仅当源/目标都是普通文件且位于支持该调用的文件系统时才生效
所以最务实的做法是:确认 shutil.copyfile() 是否已在用 copy_file_range —— 查看 strace 输出,或直接信任它,除非你明确观察到 CPU 占用异常高(说明 fallback 到了用户态循环)。
真正容易被忽略的是:大文件复制时,open() 的 buffering 参数对性能无影响,因为 shutil.copyfile() 内部强制使用二进制模式和自定义缓冲,传 buffering=0 或 8192 都会被忽略。


















