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.basename(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