User:Xyy23330121/Python/文件系统
模块 os 提供了许多有效的操作系统工具。本页省略掉其中一些操作,转而注重于操作系统中、对文件系统的操作。比如删除、复制、重命名(移动)、新建等。
除此之外,Python 还额外提供了模块 shutil 用于进行一些高阶文件操作。在一项操作可以用 shutil 中的函数完成时,用 shutil 提供的函数一般更快。
形如 C:\Program Files\Python 的,被称为是文件路径。在 Python 中,文件路径通常是由字符串存储的。比如:
path_str = r"C:\Program Files\Python" #使用“原始字符串”
Python 也支持使用抽象基类 os.PathLike 的子类来存储路径。
当一个类具有 __fspath__ 方法时,它就被视为是 os.PathLike 的子类。
__fspath__ 方法应该返回一个表示路径的字符串,比如:
class Path:
...
def __fspath__(self):
return r"C:\Program Files\Python"
传入路径参数时,可以传入路径字符串,也可以传入 os.PathLike 的子类实例。
本函数输入一个路径参数,然后返回字符串形式的路径。我们看以下示例:
class Path:
def __fspath__(self): return r"C:\Program Files\Python"
p = Path()
print(os.fspath(p)) #输出:C:\Program Files\Python
形如下面的,被称为是绝对路径。
path = r"C:\Program Files\Python"
绝对路径比较好理解,这里主要描述相对路径。
命令行程序,比如 Windows 的 cmd、PowerShell,以及 linux 的 terminal,都是有“当前工作目录”的。这是早期计算机系统,没有图形文件管理器时,用命令行程序来管理文件所需的功能。
以 PowerShell 为例,如果运行:
PS D:\test> & python.exe C:\test.py
并从 test.py 中输出“当前工作路径”。我们会发现当前工作路径为:D:\test,即调用 Python 时,PowerShell 的工作路径。而非 C:\ ,即 test.py 所在的路径。使用相对路径时要注意这一点。
相对路径是相对于当前工作路径而言的。我们同样以刚才的例子,如果在 test.py 中,用:
path = "Python"
来表示路径,则在上面的运行中,path 代表的是 D:\test\Python。
os.getcwd() 会输出当前工作路径的字符串。
os.chdir(path) 会把工作路径设为 path 所示的路径。
path 可以为表示路径的字符串,也可以为上面所述的、os.PathLike 的子类。
特别的,. 表示当前路径,而 .. 表示上一级文件夹。
比如以下的情况:
folder/
test.py
MyModule/
__init__.py
SubModule/
__init__.py为了说明简单起见,我们把工作路径设到 SubModule 文件夹中,然后看以下代码的输出:
print(os.listdir('..')) #输出:['SubModule', '__init__.py'],说明该路径代表 MyModule 文件夹。
print(os.listdir('.')) #输出:['__init__.py'],说明该路径代表 SubModule 文件夹。
print(os.listdir(r'..\..')) #输出:['MyModule', 'test.py'],说明该路径代表 folder 文件夹。
print(os.listdir(r'..\.\SubModule\..\..')) #输出:['MyModule', 'test.py'],说明该路径代表 folder 文件夹。
其中使用的函数,在本章后面会讲到。
在操作路径时,有时会出现不同系统路径分隔符不同的情况。比如,我们在路径 folder 下创建了文件夹 sub,然后想操作 sub 中的内容。使用 Windows 系统的程序员,很容易把路径写为:
folder = ...
sub = ...
path = folder "\\" sub
但是,这在使用"/"为路径分隔符的系统中,是不适用的。此时,可以用模块 os 和 os.path 中提供的内容来解决这个问题。
使用以下方法可以避免出现上面的问题:
import os
folder = ...
sub = ...
path = folder os.sep sub
os.sep会给出当前系统的路径分隔符。其它的、文件系统所使用的字符串,见下面的表格。
此类型的常量,许多在 os 模块中和 os.path 模块中都会有。
| 模块 | 系统 | 说明 | ||
|---|---|---|---|---|
| os 模块 | os.path 模块 | POSIX | Windows | |
| os.curdir | os.path.curdir | '.'
|
系统用于表示当前文件夹的常量字符串。 | |
| os.pardir | os.path.pardir | '..'
|
系统用于表示父文件夹的常量字符串。 | |
| os.sep | os.path.sep | '/' |
'\\'
|
系统路径分隔符。 |
| os.altsep | os.path.altsep | None |
'/'
|
系统路径分隔符的替代字符。 |
| os.extsep | os.path.extsep | '.'
|
系统用于分割文件名与拓展名的字符。 | |
| os.pathsep | os.path.pathsep | ':' |
';'
|
系统用于分隔搜索路径(如 PATH)中不同部分的字符 |
| os.devnull | os.path.devnull | '/dev/null' |
'nul'
|
空设备的文件路径 |
也可以用这些模块中,操作路径的函数来解决问题:
folder = ...
sub = ...
import os.path
path = os.path.join(folder, sub)
path 为一个路径。如果 path 所示的内容存在,则返回 True。特别的,对于失效的符号链接,返回 False。
除非特殊说明,以下函数的 path 参数均应传入路径。
类似 exists,但对于失效的符号链接,返回 True。
如果 path 所示的内容存在,且是一个文件,则返回 True。本方法会跟踪符号链接,因此,如果 path 所示的符号链接指向一个文件,也会返回 True。
类似 os.path.isfile,但检查的是文件夹。
如果 path 所示的内容存在,且是符号链接,则返回 True。
如果 path 所示的内容存在,且是 junction,则返回 True。junction 是 NTFS 上独有的一个类似符号链接的内容。
返回 path 中,所有子文件夹和文件的名称组成的列表。
类似 os.listdir(),但返回的并非是列表,而是一个特殊的迭代器。该迭代器中的元素为 os.DirEntry 类型。这种类型的元素包含了文件类型或文件属性信息,因此,如果代码需要查询文件类型或文件属性,用 scandir 而非 listdir 会提升性能。
迭代器的迭代顺序可能是任意顺序,而非浏览文件夹时常有的“字母顺序”或“更改时间顺序”等。
对于由 os.scandir 生成的迭代器 scandir,该序列包含一个方法 close。
scandir.close() 会关闭该迭代器,并释放占用的资源。
尽管该方法在迭代结束或出错时会自动调用。但最好还是在迭代结束后手动调用一遍该方法,或者使用 with 语句。
由 os.scandir 生成的、迭代器中的迭代元素。该元素有以下属性和方法:
文件或文件夹的名称。
文件或文件夹的路径。如果 os.scandir 的参数为绝对路径,则这里为绝对路径;如果 os.scandir 的参数为相对路径或空缺,则这里为相对路径。
相对路径是相对于使用 os.scandir 时的工作路径的。
如果是文件夹,或是指向文件夹的符号链接,则返回 True。反之,返回 False。
若 follow_symlinks 为 False,则该迭代元素指向符号链接时,也会返回 False。
类似is_dir(),如果是文件,则返回 True。
类似is_dir(),但返回详细的属性。属性将以 stat_result 类型的形式返回。
关于 stat_result 的详细信息,参见下面的章节。
返回迭代元素所对应内容的索引节点号。
如果是符号链接,返回 True。反之,返回 False。
如果是junction,则返回 True。反之,返回 False。
os.walk(top, topdown=True, onerror=None, followlinks=False)
返回一个可迭代对象。对 top 中的每个文件夹,都会在可迭代对象中增加元素 (dirpath, dirnames, filenames)。
其中,dirpath 是文件夹路径,dirnames 是文件夹中,子文件夹的名称列表,filenames 是文件夹中的文件名称列表。比如以下的情况:
folder/
test.py
MyModule/
__init__.py
SubModule/
__init__.py如果把工作路径设到 folder 所在的路径,再用 os.walk('folder'),则会有形如以下的元素被添加到可迭代对象中:
('folder', ['MyModule'], ['test.py'])
('folder\\MyModule', ['SubModule'], ['__init__.py'])
('folder\\MyModule\\SubModule', [], ['__init__.py'])若 topdown 为 True,则增加元素的方式是递归地。在迭代时,它会先生成包含 top 的三元组。下一次迭代时,存储第一次迭代时得到的三元组,再按其中 dirnames 里的子文件夹进行迭代,生成来自这些子文件夹的三元组。以此类推。
特别的,此时,在迭代过程中,可以直接对三元组中的 dirname ,使用列表的方法来进行修改,以控制之后的迭代顺序与内容。
若 topdown 为 False。则会先生成父文件夹中、所有子文件夹的三元组,再生成父文件夹。此时,更改 dirname 的内容对迭代没有影响。可以用此方法来从后往前删除空文件夹。
onerror 为一个错误处理函数。该函数输入一个 OSError 实例。在不输入此函数时,os.walk 默认会忽略它调用 os.scandir 时产生的错误。若输入了此函数,读者可以通过自定义该函数,决定出错时是报错并停止、只输出错误信息还是忽略错误。
输入 top 的内容与函数的结果有关。我们依旧是上面的例子,运行:
for obj in os.walk(r"folder\."):
print(obj)
输出为:
('folder\\.', ['MyModule'], ['test.py'])
('folder\\.\\MyModule', ['SubModule'], ['__init__.py'])
('folder\\.\\MyModule\\SubModule', [], ['__init__.py'])特别的,向 top 传入 "."、".." 等来选择工作路径,或工作路径的上级路径,也是有效的。如果工作路径在运行过程中可能会改变,最好还是对 top 使用绝对路径。
os.mkdir(path, mode=0o777, *, dir_fd=None)
本函数会创建路径为 path 的文件夹。但只创建“一层”:比如向 path 传入 C:\folder\folder,而 C:\folder 不存在时,会报错 FileNotFoundError。
如果 path 已经存在,则会报错 FileExistsError。
mode 参数对各个不同系统有区别,为了代码的可移植性,最好不要进行自定义。dir_fd 是指向目录 dirpath 的文件描述符,在一些平台上不被支持,因此也不多赘述。如果读者需要了解关于 dir_fd 的内容,参见 python文档 。
本函数会递归地创建路径为 name 的文件夹。比如向 name 传入 C:\folder\folder,而 C:\folder 不存在时,它会调用 os.mkdirs("C:\folder", mode)。
若 exist_ok 为 False,当 name 所示路径已经存在时,会报错 FileExistsError。
mode 参数对各个不同系统有区别。
本函数会删除空文件夹。仅删除一层:比如向 path 传入 C:\folder\folder 时,仅删除 C:\folder\folder,而保留 C:\folder。
如果目录不存在,则报错 FileNotFoundError。如果目录不为空,则报错 OSError。
本函数会删除空文件夹。但不止删除一层:比如向 name 传入 C:\folder\folder 时,会先删除 C:\folder\folder,再检查 C:\folder 是否是空文件夹,如果是,则继续进行删除。直到某一层不是空文件夹为止。
如果无法成功删除 name 所示的文件夹,则会报错 OSError。
本函数仅用于删除文件。
如果 path 是目录,则会报错 OSError。如果 path 不存在,则会报错 FileNotFoundError。
还有一个函数 os.unlink 和本函数完全一致。unlink 是其传统的 Unix 名称。
shutil.rmtree(path, ignore_errors=False, *, onexc=None, dir_fd=None)
此函数会删除 path 所示文件夹以及其中的所有内容。path 必须是文件夹。
如果 ignore_errors 为 True,则在删除失败时,不会进行报错。
onexc 参数是用于处理异常的函数。只有 ignore_errors 为 False 时,才会尝试让 onexc 来处理异常。
如果传入了 onexc 参数,并且在删除时引发了异常,则会将异常信息发送给该参数,并由该参数进行处理。它会按顺序传入三个信息:
function: 删除过程中,引发异常的函数。
它依赖于运行的平台和实现。path:删除过程中,function被传入的路径。excinfo:出错时,异常的实例。
如果在运行 onexc 函数时出错,则不会再把异常捕获并发送给 onexc,而是直接进行处理。
Python 的文档提供了以下示例:
删除只读文件也会导致报错。通过以下的方式,可以删除只读文件。
import os, stat
import shutil
def remove_readonly(func, path, _):
"Clear the readonly bit and reattempt the removal"
os.chmod(path, stat.S_IWRITE)
func(path)
shutil.rmtree(directory, onexc=remove_readonly)
src 和 dst 都是路径。本函数会将 src 中的内容复制到 dst。比如以下示例:
import os, stat
import shutil
shutil.copyfile("测试.txt", "测试(副本).txt")
该实例运行的结果是,新建“测试(副本).txt”,再把“测试.txt”中的全部内容复制到“测试(副本).txt”中。
如果 dst 是已经存在的文件,则会替换掉该文件。如果 src 和 dst 指向同一个文件,则会引发错误 shutil.SameFileError。
follow_symlinks 用于决定 src 是文件链接时的处理方式。如果 follow_symlinks 为 False 且 src 为符号链接,则复制的结果是一个新的符号链接,而不是 src 所指向的文件内容。
在复制完成后,本函数会返回 dst。
和 shutil.copyfile 几乎一致。只有一点不同:
如果 dst 指向一个已存在的文件夹,则会在 dst 中按 src 所示的文件名新建文件,再复制数据。
类似 shutil.copyfile ,但是会保留文件的元数据(比如修改时间等)。
保留元数据的功能并非所有平台都可以使用,读者可以按照 该文档中的描述 检查各个平台能否保留文件的相应元数据。本函数会尽可能地保留元数据,在无法保留元数据时,也不会报错。
shutil.copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False, dirs_exist_ok=False)
该函数用于直接复制整个文件夹以及其中的内容。它会把文件夹 src 中的所有内容复制到文件夹 dst 中。
具体来讲,它会先用类似 os.mkdirs 函数的方式创建 dst 目录。如果 dst 目录已存在,则会报错 FileExistsError 并停止拷贝。
如果 dir_exist_ok 为 True,则即便 dst 目录已存在,也不会报错,而是继续拷贝。
copy_function 决定了本函数会用什么函数来复制这些文件。默认使用 shutil.copy2 来复制这些文件。
上面所示的 shutil.copyfile、shutil.copy、shutil.copy2 都可以使用。如果要自定义该函数,则该函数必须能传入三个参数:
src复制文件的来源dst复制到的位置follow_symlinks是否追踪符号链接
本函数中,如果 symlinks 为 True。则复制函数中的参数 follow_symlinks 取 False。
若 symlinks 为 True,而符号链接指向的文件不存在时,本函数会在拷贝完成后报错 shutil.Error。shutil.Error 是一个包含该函数中出现的所有异常组成的异常组,它会在复制结束后被引发。
ignore 应传入一个决定哪些文件 / 文件夹不被复制的函数。
如果 ignore 不为 None,本函数的复制流程经简化后大致如下:
- 把
src中所有文件和文件夹的名称添加到“代办列表” - 把
src和“代办列表”传入ignore函数,得到“忽略列表” - 遍历“代办列表”:
- 如果在“忽略列表”中,则忽略。
- 其余的,如果是文件夹,则把该文件夹再次传入
shutil.copytree中。 - 其余的,用
copy_function来复制文件。
于是,对于被复制的一个文件夹 folder,ignore 函数会被分别传入:
os.fspath(folder),即folder的字符串形式。os.listdir(folder),即folder路径中的内容。
它应该返回 os.listdir(folder) 中元素组成的一个列表(或集合等,可以用比较运算符 in 的类型),包含不会被复制的子文件夹名称和子文件名称。
可以用这个函数创建 ignore 函数。
该函数返回的对象,会把符合 *patterns 的所有子文件名、子文件夹名都添加到“忽略列表”中。匹配是按照 模块 fnmatch 的规则进行匹配的。
移动 src 所示的文件或文件夹,使其路径变为 dst。
注意,dst 只能被创建“一层”,其父文件夹必须存在。如果对以下所示的文件树:
folder/
a.txt将工作路径设到 folder,然后执行 os.rename("a.txt",r"a\a.txt") 时,会报错。因为文件夹 folder\a 是不存在的。
对于比如 dst 已经存在的情况。该函数在不同系统中的具体表现是不同的。Python 文档列举了两个系统的以下情况:
| 状况 | Windows | Unix | ||
|---|---|---|---|---|
| dst 已经存在 | src 是文件而 dst 是目录 | 报错:FileExi
|
报错 ISADirectoryError
| |
| 两者都 是目录 |
dst 是空目录 | src 中的内容被移动到 dst 中 | ||
| dst 是非空目录 | 报错 OSError
| |||
| 两者都 是文件 |
用户有权限 | 替换掉 dst 的内容 | ||
| src 和 dst 在不同文件系统上 | 可能会失败,此时建议使用 shutil.move()
| |||
本函数和 os.rename 的关系,类似 os.mkdir 和 os.mkdirs 的关系。
如果新路径 new 所需要的父文件夹不存在,则会先把父文件夹创建完毕。
在移动完成后,将调用 removedirs 删除旧路径中的空文件夹。
类似 os.rename。但是当 dst 存在且为文件时,会对其进行替换。
先将 src 所示文件或文件夹用 copy_function 复制到 dst,再把 src 删除。最后,返回 dst。
如果 dst 指向一个已存在的文件夹。那么,在复制时,会在 dst 中按 src 所示的文件名新建文件并进行复制。
如果 dst 已存在但不是文件夹,则按照 os.rename 的覆写规则决定是覆写还是报错。
如果 src 和 dst 使用相同的文件系统,则不会使用 copy_function,而是改为直接调用 os.rename。
| 属性名 | 解释 | |
|---|---|---|
| Unix | Windows | |
| st_mode | 文件模式:文件类型和权限位 | |
| st_ino | 索引节点号 inode number | 文件索引号[3] |
| st_dev | 文件所在设备的设备标识符 | |
| st_nlink | 硬链接的数量 | |
| st_uid | 文件所有者的用户 ID | |
| st_gid | 文件所有者的用户组 ID | |
| st_size | 文件大小,以字节(B)为单位的整数 | |
| 时间戳(纪元时间) | ||
| st_atime | 最近访问时间,以秒为单位 | |
| st_atime_ns | 最近访问时间,以纳秒为单位的整数 | |
| st_mtime | 最近修改时间,以秒为单位 | |
| st_mtime_ns | 最近修改时间,以纳秒为单位的整数 | |
| st_ctime | 元数据的最近修改时间 | 不建议使用: 文件创建时间。 |
| st_ctime_ns | 类似 st_ctime,但是以纳秒为单位 | |
| st_birthtime | 文件创建时间,以秒为单位 该属性并不总是可用,使用时可能报错 AttributeError
| |
| st_birthtime_ns | 类似 st_birthtime,但是以纳秒为单位 | |
我们有以下方式查看文件元信息。
对于文件 path,返回一个包含文件元信息的对象 os.stat_result。
os.stat_result 的属性见右边。
除右侧的属性外,还可以用 stat 模块 中提供的一些方式去读取这个结果。这里不多赘述。
返回最后访问时间,为纪元秒数的浮点数。如果该文件不存在或不可访问,则报错 OSError。
返回最后修改时间,为纪元秒数的浮点数。如果该文件不存在或不可访问,则报错 OSError。
返回元数据最后修改时间,为纪元秒数的浮点数。如果该文件不存在或不可访问,则报错 OSError。
在 Windows 上,返回创建时间,为纪元秒数的浮点数。
返回文件大小,以字节为单位的整数。如果该文件不存在或不可访问,则报错 OSError。
设置文件的“最近访问时间”和“最近修改时间”。如果不指定时间,则以当前时间(纳秒)来进行设置。
如果传入 ns,则应传入一个(访问时间, 修改时间)的元组。其中每个元素是以纳秒为单位的整数(纪元时间)。
如果传入 times,则应传入一个(访问时间, 修改时间)的元组。其中每个元素是以秒为单位的整数(纪元时间)。
同时传入 ns 和 times 会出错。
设置 dst 的权限位,使其和 src 的权限位一致。
设置 dst 的权限位、最近访问时间、最近修改时间以及旗标,使其和 src 的元数据一致。
对于给出的路径 path,返回其标准化的绝对路径。
“标准化”指的是形如 folder\\foo\\.. 的内容会被简化为等效的 folder。
如果路径是绝对路径,返回 True。
在 Unix 上,这代表路径以斜杠开头。而在 Windows 上,路径可以为:
- 驱动器号+冒号开头,比如
C:和C:\Program Files - 反斜杠开头,比如
\Program Files。 - 双反斜杠开头(对于本地网络共享的文件使用的UNC路径),比如
\\host\sharename。
其它类似的判断,还有判断挂载点的 os.path.ismount(path)[4] 和判断 Windows Dev 驱动器的 os.path.isdevdrive(path)[5]。具体请参见相关文档。
对于给出的路径 path,返回其标准化的路径。
消除路径中的符号链接,返回指向文件的真实路径。
对路径的分割操作都是字符串操作。
返回路径所对应的文件名 / 文件夹名。我们有以下示例:
import os.path
path = "C:\\Program Files"
print(os.path.basename(path)) #输出:Program Files
返回路径中文件名 / 文件夹名以外的部分。该部分的结尾不会有分隔符。我们有以下示例:
import os.path
path = "C:\\Program Files"
print(os.path.dirname(path)) #输出:C:
返回 (os.path.dirname(path), os.path.basename(path))
其它类似的分割,还有 os.path.splitdrive(path)[6]、os.path.splitroot(path)[7] 和 os.path.splitext(path)[8]。这里不多赘述。
对于包含多个路径的序列 paths,输出其公共的最小父文件夹。我们有以下示例:
import os.path
paths = ["C:\\Program Files", "C:\\Program Files (x86)"]
print(os.path.commonpath(paths)) #输出:"C:\\"
对于包含多个路径的序列 paths,输出其公共的“前缀”。我们有以下示例:
import os.path
paths = ["C:\\Program Files", "C:\\ProgramData"]
print(os.path.commonpath(paths)) #输出:"C:\\Program"
将参数中开头部分的 ~ 或 ~user 替换为当前用户的用户目录,并返回替换的结果。
输入带有环境变量的路径,返回环境变量展开后的路径。对于 Windows,环境路径可以参照以下页面:
本函数会展开 path 中形如 ${name} 的部分。对于 Windows,还会展开形如 %name% 的部分。其中,name 指的是环境变量名称。
如果系统支持使用任意 unicode 字符(分隔符等除外)作为文件名,则此项为 True。
shutil.make_archive(base_name, format[, root_dir[, base_dir[, verbose[, dry_run[, owner[, group[, logger]]]]]]]) shutil.get_archive_formats() shutil.register_archive_format(name, function[, extra_args[, description]]) shutil.unregister_archive_format(name) shutil.unpack_archive(filename[, extract_dir[, format[, filter]]]) shutil.register_unpack_format(name, extensions, function[, extra_args[, description]]) shutil.unregister_unpack_format(name) shutil.get_unpack_formats()
shutil.disk_usage(path) shutil.chown(path, user=None, group=None) shutil.which(cmd, mode=os.F_OK | os.X_OK, path=None)
- ↑ 1.0 1.1 path-like object https://docs.python.org/zh-cn/3.12/glossary.html#term-path-like-object
- ↑ os.getcwdb() https://docs.python.org/zh-cn/3.12/library/os.html#os.getcwdb
- ↑ https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information?redirectedfrom=MSDN
- ↑ https://docs.python.org/zh-cn/3.13/library/os.path.html#os.path.ismount
- ↑ https://docs.python.org/zh-cn/3.13/library/os.path.html#os.path.isdevdrive
- ↑ https://docs.python.org/zh-cn/3.13/library/os.path.html#os.path.splitdrive
- ↑ https://docs.python.org/zh-cn/3.13/library/os.path.html#os.path.splitroot
- ↑ https://docs.python.org/zh-cn/3.13/library/os.path.html#os.path.splittext