ZetCode

Python 中的文件复制

最后修改于 2025 年 3 月 31 日

Python 提供了多种文件复制方法,每种方法都有不同的特性和用例。本教程涵盖了所有主要方法,包括高级实用程序、低级操作和特定于平台的注意事项。

选择正确的文件复制方法取决于您的具体需求——无论是需要保留元数据、处理大文件、跨平台兼容性,还是进度跟踪等特殊功能。我们将通过实际示例探讨每种选项。

使用 shutil.copy

shutil.copy 函数是复制文件内容的最简单方法。

shutil_copy.py
import shutil

# Basic file copy
shutil.copy('source.txt', 'destination.txt')

# Copy to directory (preserves filename)
shutil.copy('source.txt', '/path/to/directory/')

# With full paths
src = '/path/to/source.txt'
dst = '/path/to/destination.txt'
shutil.copy(src, dst)

shutil.copy 函数提供了一种直接的方式将文件内容从源复制到目标位置。此方法处理基本的文件复制操作,同时自动管理文件权限,使其适用于大多数常见用例。使用此函数时,您可以指定完整的目标文件路径或仅指定目标目录——在后一种情况下,它将保留原始文件名。

该实现具有跨平台兼容性,可在 Windows、Linux 和 macOS 系统上一致运行。但是,需要注意的是,虽然它会复制文件权限和内容,但它不会保留创建/修改时间戳等其他元数据。这使其成为您只需要复制文件内容和基本权限而无需担心保留所有原始文件属性的理想选择。

使用 shutil.copy2

shutil.copy2copy 类似,但会保留元数据。

shutil_copy2.py
import shutil

# Copy with metadata preservation
shutil.copy2('source.txt', 'destination.txt')

# Works with directories too
shutil.copy2('/path/to/src/file', '/path/to/dst/file')

shutil.copy2 函数通过在复制操作期间保留所有可能的文件元数据来扩展 shutil.copy 的功能。这包括重要的属性,如原始文件的创建和修改时间戳,这些通常对于维护文件历史记录和版本跟踪很重要。与其简单的对应项一样,它也能很好地处理文件到文件和文件到目录的复制场景。

只要底层操作系统支持这些功能,元数据保留就可以在不同的文件系统上工作。此方法在备份系统、版本控制操作或任何需要精确保留原始文件属性的情况下都特别有价值。值得注意的是,虽然它保留了大多数元数据,但根据文件系统的功能,某些特定于平台的属性仍可能不会被保留。

使用 shutil.copyfile

shutil.copyfile 仅复制文件内容。

shutil_copyfile.py
import shutil

# Copy just the file data
shutil.copyfile('source.txt', 'destination.txt')

# Will raise error if destination is directory
try:
    shutil.copyfile('source.txt', '/path/to/dir/')
except IOError as e:
    print(f"Error: {e}")

shutil.copyfile 方法仅专注于复制原始文件内容,不包含任何其他元数据或权限。这使其成为 shutil 复制函数中最轻量级的选项,因为它不尝试保留或修改任何文件系统属性。

shutil.copy 不同,此函数要求目标参数必须是完整的文件名,而不能是目录路径——尝试指定目录将引发 IOError。此行为使其在需要精确控制目标文件名时特别有用。该函数是在比其他 shutil 复制方法更低的级别实现的,从而在不需要元数据处理的简单文件内容复制任务中获得略微更好的性能。它通常用于数据处理管道,其中只有文件内容是重要的,所有其他属性将单独管理。

使用 shutil.copyfileobj

对于大文件或自定义处理,copyfileobj 复制文件对象。

shutil_copyfileobj.py
import shutil

with open('source.txt', 'rb') as src:
    with open('destination.txt', 'wb') as dst:
        shutil.copyfileobj(src, dst)

# With buffer size control (64KB chunks)
with open('largefile.iso', 'rb') as src:
    with open('copy.iso', 'wb') as dst:
        shutil.copyfileobj(src, dst, length=65536)

shutil.copyfileobj 函数通过直接处理文件对象而不是文件路径,在更基础的级别上运行。这种方法提供了几个优势,特别是在处理大文件或需要对复制过程进行细粒度控制时。通过允许您通过 length 参数指定缓冲区大小,它能够实现高效的内存管理——较大的缓冲区通常意味着较少的 I/O 操作,但内存使用量较高。

此方法在复制不同类型的文件类对象时特别有价值,例如在常规文件、网络套接字或内存缓冲区之间传输数据时。示例演示了具有默认缓冲区大小的基本用法和使用 64KB 块的优化版本,这通常在大文件操作的性能和内存效率之间提供良好的平衡。这种低级方法也为构建更复杂的复制操作(具有进度跟踪或复制过程中的数据转换)奠定了基础。

使用 os.system 进行 Shell 命令

您可以通过 os.system 调用系统复制命令。

os_system_copy.py
import os
import platform

# Platform-appropriate copy command
if platform.system() == 'Windows':
    os.system('copy source.txt destination.txt')
else:
    os.system('cp source.txt destination.txt')

# More robust version with paths
src = '/path/to/source.txt'
dst = '/path/to/destination.txt'
if platform.system() == 'Windows':
    os.system(f'copy "{src}" "{dst}"')
else:
    os.system(f'cp "{src}" "{dst}"')

使用 os.system 执行本机 shell 命令可以利用操作系统内置的文件复制功能。此方法需要特定于平台的命令语法,如 Windows 与类 Unix 系统所需的不同命令所示。第一个示例展示了一个基本的跨平台实现,该实现可在 Windows 的 copy 命令和 Unix 的 cp 命令之间切换。

第二个更健壮的版本通过将文件路径括在引号中来正确处理可能包含空格的文件路径。虽然此方法在某些场景下可能有用,但它带有几个重要的注意事项。由于涉及 shell 命令注入,该方法通常比 Python 本机方法不安全,并且正确转义路径对于防止安全漏洞至关重要。

此外,应始终检查返回值以检测和处理任何复制失败。在您专门需要 shell 功能(如通配符扩展)或与现有 shell 脚本集成的情况下,此技术可能是合适的,但对于大多数 Python 应用程序,首选本机 shutil 函数。

使用 pathlib.Path

现代 pathlib 模块提供了面向对象的文件复制。

pathlib_copy.py
from pathlib import Path

# Basic copy
src = Path('source.txt')
dst = Path('destination.txt')
src.copy(dst)

# Copy with metadata preservation
src.copy2(dst)

# Copy to directory
target_dir = Path('/path/to/directory')
src.copy(target_dir / src.name)

pathlib.Path 类(于 Python 3.4 引入)提供了一种面向对象的文件系统操作方法,包括文件复制。这个现代 API 比传统的路径字符串操作提供了几个优势。通过 Path 对象可用的复制方法反映了其 shutil 对等项的功能,但语法更优雅,并且与其他 pathlib 功能无缝集成。

该示例演示了三种常见的复制场景:基本文件复制、保留元数据的复制以及复制到目标目录同时保留原始文件名。pathlib 的面向对象特性使路径操作更加直观且不易出错,尤其是在处理复杂的路径操作时。例如,目录复制示例显示了如何轻松地使用除法运算符组合路径,该运算符会自动正确处理不同操作系统上的路径分隔符。

这种方法在现代 Python 代码库中尤其有价值,在这些代码库中,类型安全和干净的面向对象设计是优先事项,尽管值得注意的是,在底层,pathlib 的复制方法最终调用的是我们已经讨论过的相同 shutil 函数。

低级文件复制

为了完全控制,您可以手动实现文件复制。

low_level_copy.py
BUFFER_SIZE = 65536  # 64KB chunks

def copy_file(src, dst):
    with open(src, 'rb') as src_file:
        with open(dst, 'wb') as dst_file:
            while True:
                data = src_file.read(BUFFER_SIZE)
                if not data:
                    break
                dst_file.write(data)

copy_file('source.txt', 'destination.txt')

在此低级别实现文件复制可让您完全控制该过程,从而能够实现高级函数无法实现的自定义优化和特殊处理。该示例演示了一种基本但健壮的实现,该实现以可配置的块(在此情况下为 64KB)读取源文件并将其写入目标文件。

这种方法特别节省内存,因为它永远不需要一次性将整个文件加载到内存中,因此适用于可能超出可用 RAM 的非常大的文件。上下文管理器(with 语句)的使用确保了即使在复制过程中发生错误也能正确清理文件句柄。虽然这比使用 shutil 函数需要更多的代码,但它提供了添加进度跟踪、复制过程中的数据转换、自定义错误处理或与非文件数据源集成的机会。

缓冲区大小可以根据您的特定硬件的性能测试进行调整——较大的缓冲区通常会减少 I/O 操作,但会增加内存使用量。此模式还为构建可能需要处理加密文件、压缩数据或网络流的更专业的复制操作奠定了基础。

带进度跟踪的复制

对于大文件,您可能需要跟踪复制进度。

progress_copy.py
import os
import shutil

def copy_with_progress(src, dst):
    total = os.path.getsize(src)
    copied = 0
    
    def progress(length):
        nonlocal copied
        copied += length
        percent = (copied / total) * 100
        print(f"\rProgress: {percent:.1f}%", end='')
    
    with open(src, 'rb') as fsrc:
        with open(dst, 'wb') as fdst:
            shutil.copyfileobj(fsrc, fdst, callback=progress)
    print()  # Newline after progress

copy_with_progress('large_file.iso', 'copy.iso')

这个增强的复制实现演示了如何为文件复制操作添加进度跟踪,这在大文件或面向用户的应用程序中特别有价值。该函数首先确定总文件大小以建立进度计算的基线。在复制过程中,它使用一个回调函数,shutil 的 copyfileobj 在每次缓冲区传输后调用该函数,从而使我们能够更新和显示进度百分比。

进度显示使用回车符 (\r) 来反复覆盖同一行,从而在控制台中创建平滑的进度指示器。此方法在利用 shutil 的高效复制的同时,还增加了用户反馈。该实现可以通过多种方式进行扩展——例如,通过添加剩余时间估计、传输速度计算或与 GUI 进度条集成。回调机制对于在长时间运行的操作中实现取消支持也很有用。虽然此示例将输出打印到控制台,但相同的模式可以改编为更新 Web 应用程序、桌面 UI 或日志系统中的进度。最后的 print 确保进度显示完成后有一个干净的换行符。

复制目录树

要复制整个目录,请使用 shutil.copytree

copytree.py
import shutil

# Basic directory copy
shutil.copytree('source_dir', 'destination_dir')

# With symlink handling
shutil.copytree('source_dir', 'destination_dir', 
               symlinks=True)

# With ignore patterns
def ignore_pycache(dirname, filenames):
    return [fn for fn in filenames if fn == '__pycache__']

shutil.copytree('src', 'dst', ignore=ignore_pycache)

shutil.copytree 函数为递归复制整个目录结构(包括所有子目录及其内容)提供了全面的支持。基本用法只是将整个目录树从源复制到目标,在此过程中创建所有必需的子目录。symlinks 参数控制如何处理符号链接——如果为 True,它会将它们保留为链接,而不是复制它们的指向。ignore 参数接受一个可调用对象,该对象过滤应从复制操作中排除的文件和目录,例如示例中跳过 __pycache__ 目录。

此函数提供了其他几个有用的参数,此处未显示,包括为特殊文件类型指定不同复制函数的能力,或保留特定元数据属性。该实现仔细维护目录结构并处理各种边缘情况,使其比手动遍历目录更加健壮。在处理大型目录树时,请考虑将 copytree 与前面显示的进度跟踪技术结合使用,尽管这需要自定义实现,因为 copytree 不支持进度回调。此函数在备份系统、部署脚本或需要复制复杂目录结构的任何应用程序中都特别有价值。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一名热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在教授编程方面拥有十多年的经验。

列出所有 Python 教程