Shutil是一个Python内置的用来高效处理文件和目录迁移任务的库。Shutil不仅支持基本的文件复制、移动和删除操作,还具备处理大文件、批量迁移目录、以及跨平台兼容性等特性。通过使用Shutil,我们可以更加轻松地实现文件系统的管理和维护,本文我将讲解该库的使用方法以需要注意的地方。
目录
StrPath与BytesPath
文件元数据(MetaData)
基础元数据
时间戳元数据
权限元数据
系统级元数据
符号链接
符号链接基本含义
什么时候用符号链接
复制文件
shutil.copy
可能出现的错误
shutil.copyfile
可能出现的错误
shutil.copy2
可能出现的错误
移动文件
shutil.move
可能出现的错误:
删除目录
shutil.rmtree
总结:
StrPath与BytesPath
在使用shutil内置的函数时,部分函数观察其注释可以发现传入的路径参数有两种。分别是字符串路径StrPath与BytesPath,这里我们来讲一下二者的区别。
BytesPath是路径的二进制表示形式,通常用于:
- 处理 非 UTF-8 编码的文件名(如某些特殊字符或非标准编码的文件系统)
- 与底层操作系统 API 交互时(某些系统调用需要二进制格式的路径)
其实就是字符串的字节码,我们可以使用encode函数来将一个字符串转换为字节码
StrPath=r"E:\Desktop\lec01.pptx"
print(StrPath)
BytesPath=r"E:\Desktop\lec01.pptx".encode('utf-8')#utf-8是通用的编码方式
print(BytesPath)
对于纯英文路径来说将encode为字节码之后,即变为Bytes型路径后其输出结果为:b‘原路径’
当路径中含有非英文单词时,将其encode为字节码之后, 即变为Bytes型路径后其输出结果:
中文字符课程被encode为utf- 8类型的字节码是'\xe8\xaf\xbe\xe7\xa8\x8b',其余部分保持不变
文件元数据(MetaData)
文件元数据是描述文件属性的数据,包含除文件内容本身外的所有信息。在Windows系统中可以通过右键文件点击属性在属性面板中查看
基础元数据
元数据类型 | 说明 | 查看方法(Python) |
---|---|---|
文件名 | 文件的名称 | os.path.basename(filepath) |
文件大小 | 文件的字节大小 | os.path.getsize(filepath) |
文件类型 | 文件扩展名/格式 | os.path.splitext(filepath)[1] |
文件路径 | 文件的完整存储路径 | os.path.abspath(filepath) |
时间戳元数据
时间戳类型 | 说明 | Unix对应字段 | Windows对应字段 |
---|---|---|---|
创建时间(ctime) | 文件创建时间 | st_ctime | st_ctime |
修改时间(mtime) | 文件内容最后修改时间 | st_mtime | st_mtime |
访问时间(atime) | 文件最后被访问时间 | st_atime | st_atime |
元数据修改时间 | 文件属性(非内容)最后修改时间(Unix特有) | st_ctime | - |
权限元数据
权限类型 | 说明 | Unix查看 | Windows查看 |
---|---|---|---|
文件模式 | 读写执行权限(rwx) | stat.st_mode | 文件属性→安全选项卡 |
用户ID(UID) | 文件所有者ID | stat.st_uid | - |
组ID(GID) | 文件所属组ID | stat.st_gid | - |
访问控制列表(ACL) | 更精细的权限控制 | getfacl 命令 | 文件属性→安全→高级 |
系统级元数据
元数据类型 | 说明 | 查看方法 |
---|---|---|
设备ID | 文件所在的设备标识符 | stat.st_dev |
inode编号 | 文件系统索引节点号(Unix) | stat.st_ino |
硬链接数 | 指向该文件的硬链接数量 | stat.st_nlink |
文件系统标志 | 文件特殊属性(如只读、隐藏等) | stat.st_flags (Unix) |
上述表格中提到的元数据都可以使用os库来查看,具体代码如下:
import os
from datetime import datetime
s=os.stat(r'BG.py')
print("="*50)
print(f"大小: {s.st_size} 字节")
print(f"设备: {s.st_dev}")
print(f"inode: {s.st_ino}")
print(f"硬链接数: {s.st_nlink}")
print(f"权限: {oct(s.st_mode)}")
print(f"所有者UID: {s.st_uid}")
print(f"所属组GID: {s.st_gid}")
print(f"创建时间: {datetime.fromtimestamp(s.st_ctime)}")
print(f"修改时间: {datetime.fromtimestamp(s.st_mtime)}")
print(f"访问时间: {datetime.fromtimestamp(s.st_atime)}")
当然,shutil某些函数在对文件操作时也会涉及到对上述元数据的操作。
符号链接
在使用shutil的一些函数时,大家可能会看到follow_symlinks类似的参数,这里的symlinks指的是符号链接,所谓符号链接(Symbolic Link,也叫软链接)就像电脑里的"快捷方式"或"替身",但它比普通的快捷方式更强大。
符号链接基本含义
使用一个不恰当的比喻, 符号链接其实就像是个书签 📖。
想象你在看一本很厚的书,你在第1页写了个笔记:"重要内容见第500页"。那么这个笔记其实就是一个符号链接:
- 实际文件 = 第500页的内容
- 符号链接 = 第1页的这个笔记
你通过这个"笔记"能直接找到真正的内容
他的特点就是:
- 文件大小很小(就像书签只占一点点位置)
- 删除符号链接不会影响原文件(撕掉书签不会删除第500页)
这实际与我们安装应用时创建的快捷方式类似,当然二者还是有一些区别的,以下是二者的区别:
特性 | 符号链接 | 普通快捷方式 |
---|---|---|
系统层级 | 文件系统级别 | 应用级别 |
兼容性 | 所有程序都能识别 | 部分程序识别 |
跨设备 | 可以指向网络位置 | 通常只能本地 |
什么时候用符号链接
-
节省空间:同一个大文件需要在多个位置使用
比如:你的电影库实际存放在D盘,但在C盘的"我的影片"文件夹里创建链接
-
版本切换:快速切换不同版本软件
比如:
python
链接可以指向python3.8
或python3.9
-
系统维护:不改动原有结构的情况下调整文件位置
复制文件
当我们使用shutil复制文件时,共有以下函数可以用来复制文件,他们的主要作用如下表所示:
函数名称 | 功能描述 |
---|---|
shutil.copy(src, dst) | 复制文件到目标路径(dst 可以是目录或新文件名),不保留文件元数据 |
shutil.copyfile(src, dst) | 仅复制文件内容,dst 必须是完整文件名且不能已存在,否则报错 |
shutil.copytree(src, dst) | 递归复制整个目录树(包括子目录),要求目标目录dst 必须不存在 |
shutil.copy2(src, dst) | 功能同copy() ,但会保留文件元数据(如修改时间、权限等) |
shutil.copymode(src, dst) | 仅复制文件的权限模式(不复制内容或元数据) |
shutil.copyfileobj(fsrc, fdst) | 在文件对象级别复制内容(需手动打开文件对象) |
shutil.copystat(src, dst) | 仅复制文件的元数据(权限、时间戳等),不复制文件内容 |
shutil.copy
参数详解:
参数 | 含义 | 类型 |
---|---|---|
src | 待复制的源文件路径字符串或Bytes字节码路径 | str or bytes str |
dst | 目标路径的字符串或Bytes字节码路径(可以是文件夹也可以是文件名) | str or bytes str |
follow_symlinks | 复制符号链接指向的实际文件内容还是复制符号链接本身 | bool |
代码:
需要注意的是shutil.copy只会复制文件内容,并不复制元数据,像修改时间等这些元数据都是运行代码后产生的
src路径下文件:
import os
import shutil
shutil.copy(src=r"E:\Desktop\文件保存\26162605op6m.pdf",dst=os.getcwd())
运行代码后dst路径下文件:
可以看到最近修改时间变成了实际运行代码时间
可能出现的错误
Permission Error(dst路径下已包含src路径下的内容):
需要注意的是dst路径为目录时其内部不能含有src路径下的同名文件,否则会出现Permission Error,这其实与我们试图将一个同名文件复制到已经包含该文件的文件夹时弹出的提示类似,只不过shutil并不能决定你是否选择替换还是跳过,而是直接抛出异常。
运行第一次后,当前目录下已经包含src路径下的文件,再次运行便会出现Permission Error
shutil.copyfile
shutil.copyfile与shuitl.copy函数唯一不同的地方在于它的dst路径必须是文件名且不能在dst路径中已存在(新建一个空白的也不行),而shutil.copy则既可以是目录名也可以是文件名(是文件名的时候内容空白或名字重复无所谓)。当然,shutil.copyfile函数也只会复制文件内容,并不复制元数据,像修改时间等这些元数据都是运行代码后产生的。
参数详解:
参数 | 含义 | 类型 |
---|---|---|
src | 待复制的源文件路径字符串 | str |
dst | 目标路径的字符串(必须是文件名且不能已存在) | str |
follow_symlinks | 复制符号链接指向的实际文件内容还是复制符号链接本身 | bool |
代码:
src路径下文件:
import os
import shutil
dst=os.path.join(os.getcwd(),"小谢的selenium操作手册.docx")
shutil.copyfile(src=r"E:\Desktop\测试开发\小谢的selenium操作手册.docx",dst=dst)
运行代码后dst路径下文件:
可以看到最近修改时间变成了实际运行代码时间
可能出现的错误
Permission Error(dst路径下已包含src路径下的内容):
需要注意的是dst路径下不能含有src路径下的同名文件,否则会出现Permission Error,这其实与我们试图将一个同名文件复制到已经包含该文件的文件夹时弹出的提示类似,只不过shutil并不能决定你是否选择替换还是跳过,而是直接抛出异常。
运行第一次后,当前目录下已经包含src路径下的文件,再次运行便会出现Permission Error
shutil.copy2
shutil.copy2函数的参数与shutil.copy完全一致,二者唯一的区别是shutil.copy2会一同复制元数据。
参数详解:
参数 | 含义 | 类型 |
---|---|---|
src | 待复制的源文件路径字符串或Bytes字节码路径 | str or bytes str |
dst | 目标路径的字符串或Bytes字节码路径(可以是文件夹也可以是文件名) | str or bytes str |
follow_symlinks | 复制符号链接指向的实际文件内容还是复制符号链接本身 | bool |
代码:
src路径下文件:
import os
import shutil
dst=shutil.copy2(src=r"E:\Desktop\测试开发\小谢的selenium操作手册.docx",dst=os.getcwd())
运行代码后dst路径下文件:
元数据一模一样(这里只看修改时间便可以看出)。
可能出现的错误
Permission Error(dst路径下已包含src路径下的内容):
需要注意的是dst路径为目录时该目录下不能含有src路径下的同名文件,否则会出现Permission Error,这其实与我们试图将一个同名文件复制到已经包含该文件的文件夹时弹出的提示类似,只不过shutil并不能决定你是否选择替换还是跳过,而是直接抛出异常。
运行第一次后,当前目录下已经包含src路径下的文件,再次运行便会出现Permission Error
移动文件
shutil.move
参数详解:
参数 | 含义 | 类型 |
---|---|---|
src | 待复制的源文件字符串或Bytes字节码路径 | str |
dst | 目标路径的字符串或Bytes字节码路径 | str |
copy_funciton | 复制文件回调函数,默认是shuti.copy2 | callable |
代码:
import os
import shutil
shutil.move(src=r"E:\Desktop\文件保存\.docx",dst=os.getcwd())
关于copy_function这一参数的说明:
shutil.move函数文档注释内容
所谓移动文件其实就是先把文件复制到目标路径下,然后再把原路径下的文件删除。而将文件复制到目标路径下这一功能无非就是使用shutil.copy,shutil.copy2等函数来实现,正如上边注释所言:可选的 'copy_function' 参数是将要使用的可调用复制源,否则它将被委托给 'copytree'。默认情况下,使用 copy2()
可能出现的错误:
Permission Error(src路径下的文件已被打开):
在任何操作系统上我们都无法对一个已经被打开的文件进行迁移操作,如果src路径下的文件被打开,那么自然也会抛出PermissionError的错误。
Error(dst路径下已经有一个与src传入路径下同一个basename的文件)
dst路径下有一个名为test.docx的文件
提示test.docx already exists
删除目录
shutil.rmtree是shutil模块中唯一用来删除文件的函数,当然可以用来删除文件的函数还有很多,比如os模块下的os.remove,os.rmdir,下表给出了这几三者之间的区别:
函数 | 能删文件 | 能删空目录 | 能删非空目录 | 适用场景 |
---|---|---|---|---|
shutil.rmtree() | ❌ 不能 | ✔️ 可以 | ✔️ 可以 | 仅用于删除目录树(彻底删除内部所有文件夹及内容) |
os.remove() | ✔️ 可以 | ❌ 不能 | ❌ 不能 | 仅用于删除单个文件 |
os.rmdir() | ❌ 不能 | ✔️ 可以 | ❌ 不能 | 仅用于删除空目录 |
shutil.rmtree
参数详解:
参数 | 类型 | 默认值 | 功能描述 |
---|---|---|---|
path | str | 必填 | 要删除的目录路径,需要注意的是路径必须是目录且不为空 |
ignore_errors | bool | False | 是否忽略删除过程中可能产生的各种错误 |
onerror | callable | None | 错误处理回调函数 |
dir_fd | int | None | 目录文件描述符(仅Unix) |
onexc (Python 3.12+) | callable | None | 异常处理回调函数(新版替代 |
在该文件夹下,我创建了3层深度的文件夹,并在内部新建了一个txt类型文件
代码 :
import shutil
shutil.rmtree(r"E:\Desktop\rmtree测试",ignore_errors=True)
运行代码后,该文件夹被彻底删除!但是os.remove和os.remdir对此却无能为力
总结:
shutil是一个python内置的用来进行文件迁移的模块,该模块本质上是对os模块处理文件相关操作的二次开发,比如shutil.copyfile函数,其实就是在使用with open语句进行两次io操作:先读取src内容,再将读取内容写入到dst路径。
类似这样的操作我们自己也可以写出来,只不过并不一定会考虑到各种可能存在的异常情况以及跨平台兼容性等问题,而shutil则已经将这些情况全部考虑并封装成为了‘’轮子‘’供我们直接调用,快捷而方便。当然,这也是Python的特色之一。
我想要驾驶一辆汽车,难道还需要自己制造轮胎吗?