os 模块
当程序需要读取目录、创建文件夹、判断文件是否存在,或者遍历一整棵目录树时,如何确定路径基准并安全修改文件系统?os 模块提供了这些与操作系统交互的基础能力。
本章主要学习三类操作:
- 使用
os管理当前工作目录和文件系统对象 - 使用
os.path处理传统字符串路径 - 使用
os.walk()递归遍历目录
后面的 pathlib 章节会介绍更现代的路径对象,shutil 章节会负责复制、移动和递归删除。本章先建立文件系统操作的基础概念。
工作目录
os.getcwd() 返回程序当前的工作目录:
import os
current_dir = os.getcwd()
print(current_dir)相对路径默认从当前工作目录开始解析。假设当前工作目录是项目根目录:
open("data/student.txt", encoding="utf-8")Python 会在“项目根目录下的 data 目录”中查找文件,不一定从当前 .py 文件所在目录开始查找。
路径基准
- 相对路径从当前工作目录开始,例如
data/student.txt。 - 绝对路径从磁盘根目录开始,完整描述目标位置。
图中展示了 .. 表示上一级目录的层级关系。如果在 Python 中使用图里的相对路径,它仍然会根据当前工作目录解析,除非代码先计算出当前文件所在目录。
__file__ 表示当前 Python 文件的路径,但它不保证一定是绝对路径。可以主动转换:
import os
script_path = os.path.abspath(__file__)
script_dir = os.path.dirname(script_path)
print(script_path)
print(script_dir)这样就能以当前文件所在目录为起点,定位旁边的资源:
data_path = os.path.join(script_dir, "data", "student.txt")工作目录切换
os.chdir(path) 可以切换当前工作目录:
import os
original_dir = os.getcwd()
script_dir = os.path.dirname(os.path.abspath(__file__))
try:
os.chdir(script_dir)
print(os.getcwd())
finally:
os.chdir(original_dir)切换工作目录会影响整个程序之后的相对路径。确实需要使用时,应该保存原目录,并通过 finally 保证恢复。一般项目更适合计算目标路径,而不是频繁调用 chdir()。
目录内容读取
os.listdir(path) 返回指定目录下的名称列表:
import os
for name in sorted(os.listdir(".")):
print(name). 表示当前工作目录。listdir() 返回的是名称,不是完整路径。如果还要判断每一项是文件还是目录,需要先拼出完整路径:
import os
for name in sorted(os.listdir(".")):
item_path = os.path.join(".", name)
if os.path.isfile(item_path):
print(f"文件:{name}")
elif os.path.isdir(item_path):
print(f"目录:{name}")如果指定目录不存在,listdir() 会抛出 FileNotFoundError。
目录创建
os.mkdir() 创建一级目录:
import os
os.mkdir("demo_workspace")目录已经存在时会抛出 FileExistsError;上级目录不存在时也无法直接创建。
需要一次创建多级目录时,可以使用 os.makedirs():
import os
os.makedirs("demo_workspace/data/images", exist_ok=True)exist_ok=True 表示目标目录已经存在时不报错。但如果同名路径是一个文件,仍然会抛出异常。
文件系统修改
os 提供了几个基础文件系统操作:
| 方法 | 作用 | 重要限制 |
|---|---|---|
os.rename() | 重命名文件或目录 | 目标冲突时的行为可能因系统而异 |
os.remove() | 删除文件 | 不能删除目录 |
os.rmdir() | 删除目录 | 只能删除空目录 |
这些操作会直接修改文件系统。下面使用自动清理的临时目录演示,避免误操作真实文件:
import os
import tempfile
with tempfile.TemporaryDirectory() as workspace:
data_dir = os.path.join(workspace, "data")
os.mkdir(data_dir)
old_path = os.path.join(data_dir, "old.txt")
new_path = os.path.join(data_dir, "new.txt")
with open(old_path, "w", encoding="utf-8") as file:
file.write("演示内容")
os.rename(old_path, new_path)
print(os.listdir(data_dir))
os.remove(new_path)
os.rmdir(data_dir)TemporaryDirectory() 会创建专门的临时工作区,并在代码块结束后自动清理。这里引入它只是为了让删除和改名示例能够安全运行。
删除前应先确认目标路径来自哪里,并且限制在明确的工作目录中。需要删除包含内容的整个目录时,应使用后续 shutil 章节介绍的方法。
字符串路径处理
os.path 把路径作为字符串处理,并根据当前操作系统使用合适的路径规则。
路径拼接与绝对化
不要手动拼接 / 或 \\,应使用 os.path.join():
import os
relative_path = os.path.join("data", "images", "logo.png")
absolute_path = os.path.abspath(relative_path)
print(relative_path)
print(absolute_path)abspath() 会根据当前工作目录把相对路径转换成绝对路径,因此程序从不同目录启动时,结果可能不同。
路径拆分
import os
file_path = os.path.join("course", "notes", "chapter.md")
print(os.path.dirname(file_path))
print(os.path.basename(file_path))
print(os.path.splitext(file_path))这些方法分别得到目录部分、最后一段名称,以及文件名与扩展名:
course/notes
chapter.md
('course/notes/chapter', '.md')实际路径分隔符会随操作系统变化。
路径状态判断
import os
target_path = os.path.join("data", "student.txt")
print(os.path.exists(target_path))
print(os.path.isfile(target_path))
print(os.path.isdir(target_path))| 方法 | 判断内容 |
|---|---|
os.path.exists() | 路径是否存在 |
os.path.isfile() | 路径是否存在并且是文件 |
os.path.isdir() | 路径是否存在并且是目录 |
判断结果只能反映调用时的状态。判断完成后,其他程序仍然可能修改文件系统,因此真正执行操作时依然要处理可能出现的异常。
目录递归遍历
os.listdir() 只查看一层目录。如果要遍历任意深度的目录,可以使用 os.walk():
import os
for current_dir, dir_names, file_names in os.walk("sample_files"):
print(f"当前目录:{current_dir}")
print(f"子目录:{dir_names}")
print(f"文件:{file_names}")每轮遍历会得到三个内容:
current_dir:当前正在访问的目录dir_names:当前目录中的子目录名称file_names:当前目录中的文件名称
不需要自己编写固定层数的嵌套循环,os.walk() 会继续进入每一个子目录。
综合案例:统计目录内容
需求: 扫描示例目录,列出每个子目录中的文件,并统计目录和文件数量;程序只能读取内容,不能修改或删除文件。
功能分析: 以 main.py 所在目录为路径基准,使用 os.walk() 递归遍历,使用相对路径整理输出,并分别累计目录和文件数量。
准备下面的示例目录:
01-directory-inventory/
├── main.py
└── sample_files/
├── data/
│ └── scores.csv
├── python/
│ └── demo.py
└── readme.txt按目录树创建文件。readme.txt 写入 目录遍历示例文件。,另外两个文件的内容如下:
name,score
张三,90
李四,85print("目录遍历示例")程序根据 main.py 所在位置找到 sample_files,所以无论从哪个工作目录启动,都能定位到示例数据:
import os
script_dir = os.path.dirname(os.path.abspath(__file__))
base_dir = os.path.join(script_dir, "sample_files")
directory_count = 0
file_count = 0
for current_dir, dir_names, file_names in os.walk(base_dir):
dir_names.sort()
file_names.sort()
relative_dir = os.path.relpath(current_dir, base_dir)
print(f"[{relative_dir}]")
directory_count += len(dir_names)
file_count += len(file_names)
for filename in file_names:
print(f"- {filename}")
print(f"目录数量:{directory_count}")
print(f"文件数量:{file_count}")运行结果:
[.]
- readme.txt
[data]
- scores.csv
[python]
- demo.py
目录数量:2
文件数量:3案例只读取目录,没有修改或删除文件,适合反复运行。
文件系统安全边界
执行创建、改名或删除操作前,至少确认下面几件事:
- 当前工作目录是不是预期目录。
- 相对路径最终会指向哪里。
- 删除目标是文件、空目录还是包含内容的目录。
- 目标是否限制在专门的演示或工作目录中。
- 文件系统发生变化时,代码是否能正确处理异常。
基础阶段不要对用户主目录、课程根目录或来源不明的路径执行批量删除。
路径与文件操作分工
| 模块 | 本课程中的主要职责 |
|---|---|
os | 当前工作目录、基础文件操作、目录遍历 |
os.path | 阅读传统字符串路径代码 |
pathlib | 新项目中的路径表达、拼接和判断 |
shutil | 复制、移动和递归删除 |
掌握 os.path 是为了能够维护已有代码。编写新项目时,后面学习的 pathlib 通常会让路径操作更清晰。
小结
- 相对路径默认从当前工作目录开始,不是从当前 Python 文件所在目录开始。
__file__不保证是绝对路径,可以使用os.path.abspath(__file__)转换。listdir()查看一层目录,walk()可以递归遍历任意深度。mkdir()创建一级目录,makedirs()可以创建多级目录。rename()、remove()和rmdir()会直接修改文件系统,运行前必须确认目标。os.path可以拼接、拆分并判断字符串路径。- 新项目中的路径处理会在后续章节优先使用
pathlib。
练习
- 使用
os.getcwd()输出当前工作目录,再把一个相对路径转换成绝对路径。 - 使用
os.listdir()分别打印指定目录中的文件和子目录。 - 在
TemporaryDirectory()中创建、重命名并删除一个测试文件。 - 使用
os.path拆分一个路径,输出目录、文件名和扩展名。 - 修改综合案例,分别统计
.py、.txt和.csv文件的数量。