Shutil 标准库: Python 文件操作的万用刀

开发 后端
今天来聊一个被低估的 Python 标准库 - shutil 。工作中我们用 Python (尤其是写一些短小轻快的脚本)虽然经常和文件打交道,却很少用到 shutil 。但实际上, shutil 提供了比 os 模块更高级的文件操作接口,能让我们写出更 Pythonic 的代码。

今天来聊一个被低估的 Python 标准库 - shutil 。工作中我们用 Python (尤其是写一些短小轻快的脚本)虽然经常和文件打交道,却很少用到 shutil 。但实际上, shutil 提供了比 os 模块更高级的文件操作接口,能让我们写出更 Pythonic 的代码。

从一个真实场景说起

最近在整理项目代码时,需要将散落在各处的配置文件归类到统一目录。按以往的习惯,我会这样写:

import os

# 创建目标目录
if not os.path.exists("configs"):
    os.makedirs("configs")

# 移动文件
for root, dirs, files in os.walk("."):
    for file in files:
        if file.endswith(".conf"):
            src = os.path.join(root, file)
            dst = os.path.join("configs", file)
            os.rename(src, dst)

这段代码能完成任务,但存在几个问题:

  • 如果目标路径已存在同名文件会报错
  • 不支持跨设备移动
  • 没有保留文件的元数据(权限、时间戳等)

用 shutil 可以优雅地解决这些问题:

import shutil
import os

os.makedirs("configs", exist_ok=True)
for root, dirs, files in os.walk("."):
    for file in files:
        if file.endswith(".conf"):
            src = os.path.join(root, file)
            dst = os.path.join("configs", file)
            shutil.move(src, dst)

看起来差别不大,但 shutil.move() 会:

当目标路径已存在同名文件时, shutil.move() 的行为取决于操作系统

  • Windows:如果目标文件存在且正在使用,会抛出 PermissionError ;否则会静默覆盖目标文件
  • Unix/Linux:会遵循操作系统的规则。如果用户有权限,会覆盖目标文件;否则抛出 PermissionError

支持跨设备移动

"跨设备"指的是在不同的文件系统或存储设备之间移动文件,比如从 C 盘移动到 D 盘、从本地磁盘移动到网络驱动器、从固态硬盘移动到 U 盘。

shutil.move() 会首先尝试使用 os.rename() ,如果失败且错误是跨设备错误 errno.EXDEV ,则复制文件到目标位置,验证复制成功,最后删除源文件。

保留源文件的所有元数据。

shutil 常用操作详解

1. 复制文件和目录

# 复制文件
shutil.copy("source.txt", "dest.txt")      # 复制文件内容
shutil.copy2("source.txt", "dest.txt")     # 复制文件内容和元数据

# 复制目录
shutil.copytree("src_dir", "dst_dir")      # 递归复制整个目录树

copy2() 比 copy() 多了preserving metadata 的功能,在需要保留文件属性时很有用。

2. 删除目录

# 删除目录树
shutil.rmtree("dir_to_remove")             # 递归删除目录及其内容

比 os.rmdir() 强大,后者只能删除空目录。

3. 磁盘使用统计

total, used, free = shutil.disk_usage(".")
print(f"总空间: {total // (2**30)} GiB")
print(f"已使用: {used // (2**30)} GiB")
print(f"可用: {free // (2**30)} GiB")

直观地获取磁盘使用情况,免去了手动计算的麻烦。

4. 文件打包与压缩

# 创建压缩包
shutil.make_archive("backup", "zip", "source_dir")   # 支持zip、tar等格式

# 解压缩
shutil.unpack_archive("backup.zip", "extract_dir")

实用脚本示例

1. 项目备份工具

import shutil
from datetime import datetime
import os

def backup_project(project_path, backup_dir="backups"):
    # 创建以时间戳命名的备份文件
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup_name = f"backup_{timestamp}"
    
    # 确保备份目录存在
    os.makedirs(backup_dir, exist_ok=True)
    
    # 创建压缩包
    archive_path = shutil.make_archive(
        os.path.join(backup_dir, backup_name),
        "zip",
        project_path
    )
    
    print(f"备份完成: {archive_path}")

2. 大文件搬运工具

import shutil
import os
from pathlib import Path

def move_large_files(src_dir, dst_dir, min_size_mb=100):
    """移动大于指定大小的文件到目标目录"""
    dst_path = Path(dst_dir)
    dst_path.mkdir(exist_ok=True)
    
    for root, _, files in os.walk(src_dir):
        for file in files:
            file_path = Path(root) / file
            if file_path.stat().st_size > min_size_mb * 1024 * 1024:
                try:
                    shutil.move(str(file_path), dst_path / file)
                    print(f"已移动: {file}")
                except Exception as e:
                    print(f"移动失败 {file}: {e}")

3. 智能文件分类器

import shutil
from pathlib import Path
import mimetypes

def organize_files(directory):
    """根据文件类型自动分类文件"""
    directory = Path(directory)
    
    # 遍历所有文件
    for file_path in directory.rglob("*"):
        if file_path.is_file():
            # 获取文件类型
            mime_type, _ = mimetypes.guess_type(str(file_path))
            if mime_type:
                category = mime_type.split("/")[0]
                
                # 创建分类目录
                dest_dir = directory / category
                dest_dir.mkdir(exist_ok=True)
                
                # 移动文件
                try:
                    shutil.move(str(file_path), str(dest_dir / file_path.name))
                except Exception as e:
                    print(f"处理{file_path}时出错: {e}")

性能提示

对于大文件操作,shutil 提供了 copyfileobj() 方法,支持设置缓冲区大小:

with open("source.dat", "rb") as fsrc:
    with open("dest.dat", "wb") as fdst:
        shutil.copyfileobj(fsrc, fdst, length=1024*1024)  # 1MB buffer

copytree() 支持多进程并行复制:

from multiprocessing import Pool

def copy_with_progress(src, dst):
    shutil.copy2(src, dst)
    return dst

with Pool(processes=4) as pool:
    shutil.copytree("src_dir", "dst_dir", copy_function=pool.map)

文件属性操作

1. 权限和所有权

import shutil
import os

def mirror_permissions(src, dst):
    # 复制权限位
    shutil.copymode(src, dst)
    
    # 复制所有权(需要root权限)
    try:
        shutil.chown(dst, 
                     user=os.stat(src).st_uid,
                     group=os.stat(src).st_gid)
    except PermissionError:
        print("需要管理员权限来修改所有权")

2. 元数据复制

import shutil
import os
from datetime import datetime

def show_metadata(path):
    stat = os.stat(path)
    print(f"访问时间: {datetime.fromtimestamp(stat.st_atime)}")
    print(f"修改时间: {datetime.fromtimestamp(stat.st_mtime)}")
    print(f"创建时间: {datetime.fromtimestamp(stat.st_ctime)}")
    print(f"权限: {oct(stat.st_mode)[-3:]}")
    print(f"大小: {stat.st_size} bytes")

# 复制文件并保留所有元数据
src = "source.txt"
dst = "dest.txt"
shutil.copy2(src, dst)

print("源文件元数据:")
show_metadata(src)
print("\n目标文件元数据:")
show_metadata(dst)

总结

shutil 是一个设计优雅的文件操作库:

  • 提供了比 os 模块更高级的接口
  • 自动处理各种边界情况
  • 保持了 Python "batteries included" 的理念

下次遇到文件操作需求,不妨先看看 shutil 是否已经提供了合适的工具。毕竟,"不要重复发明轮子"也是 Python 的哲学之一。

责任编辑:姜华 来源: Piper蛋窝
相关推荐

2011-01-20 17:34:01

IPv6雅虎

2019-05-17 10:10:30

优衣库黑客数据泄漏

2021-04-15 07:53:15

ParkMobile数据泄露网络犯罪

2010-02-21 18:33:28

文件夹病毒照片影像U盘病毒

2024-01-09 07:42:46

Shutil 模块Python 编程工具

2023-05-30 20:19:20

2022-09-05 11:25:22

恶意浏览器Chrome恶意扩展

2011-12-26 15:31:33

2013-12-12 17:03:57

Lua脚本语言

2011-12-22 10:58:37

2021-04-02 11:09:35

MobiKwik 移动支付数据泄露

2010-05-04 22:32:37

手机木马网络安全360手机卫士

2009-02-01 21:11:13

Facebook存储数据中心

2015-02-09 10:14:33

2020-07-29 15:09:56

Dave数据泄露数据库泄露

2023-06-13 15:55:54

2023-01-31 17:07:06

2012-07-04 09:28:41

我查查推广运营Mary

2022-09-01 11:21:06

扩展恶意代码

2012-08-01 10:45:40

Outlook
点赞
收藏

51CTO技术栈公众号