Skip to content

os 模块 ​

当程序需要读取目录、创建文件夹、判断文件是否存在,或者遍历一整棵目录树时,如何确定路径基准并安全修改文件系统?os 模块提供了这些与操作系统交互的基础能力。

本章主要学习三类操作:

  • 使用 os 管理当前工作目录和文件系统对象
  • 使用 os.path 处理传统字符串路径
  • 使用 os.walk() 递归遍历目录

后面的 pathlib 章节会介绍更现代的路径对象,shutil 章节会负责复制、移动和递归删除。本章先建立文件系统操作的基础概念。

工作目录 ​

os.getcwd() 返回程序当前的工作目录:

python
import os

current_dir = os.getcwd()
print(current_dir)

相对路径默认从当前工作目录开始解析。假设当前工作目录是项目根目录:

python
open("data/student.txt", encoding="utf-8")

Python 会在“项目根目录下的 data 目录”中查找文件,不一定从当前 .py 文件所在目录开始查找。

路径基准 ​

  • 相对路径从当前工作目录开始,例如 data/student.txt。
  • 绝对路径从磁盘根目录开始,完整描述目标位置。

相对路径中的目录层级与工作目录基准

图中展示了 .. 表示上一级目录的层级关系。如果在 Python 中使用图里的相对路径,它仍然会根据当前工作目录解析,除非代码先计算出当前文件所在目录。

__file__ 表示当前 Python 文件的路径,但它不保证一定是绝对路径。可以主动转换:

python
import os

script_path = os.path.abspath(__file__)
script_dir = os.path.dirname(script_path)

print(script_path)
print(script_dir)

这样就能以当前文件所在目录为起点,定位旁边的资源:

python
data_path = os.path.join(script_dir, "data", "student.txt")

工作目录切换 ​

os.chdir(path) 可以切换当前工作目录:

python
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) 返回指定目录下的名称列表:

python
import os

for name in sorted(os.listdir(".")):
    print(name)

. 表示当前工作目录。listdir() 返回的是名称,不是完整路径。如果还要判断每一项是文件还是目录,需要先拼出完整路径:

python
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() 创建一级目录:

python
import os

os.mkdir("demo_workspace")

目录已经存在时会抛出 FileExistsError;上级目录不存在时也无法直接创建。

需要一次创建多级目录时,可以使用 os.makedirs():

python
import os

os.makedirs("demo_workspace/data/images", exist_ok=True)

exist_ok=True 表示目标目录已经存在时不报错。但如果同名路径是一个文件,仍然会抛出异常。

文件系统修改 ​

os 提供了几个基础文件系统操作:

方法作用重要限制
os.rename()重命名文件或目录目标冲突时的行为可能因系统而异
os.remove()删除文件不能删除目录
os.rmdir()删除目录只能删除空目录

文件与目录增删操作决策与异常防范

这些操作会直接修改文件系统。下面使用自动清理的临时目录演示,避免误操作真实文件:

python
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 路径解剖与跨平台安全组装

路径拼接与绝对化 ​

不要手动拼接 / 或 \\,应使用 os.path.join():

python
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() 会根据当前工作目录把相对路径转换成绝对路径,因此程序从不同目录启动时,结果可能不同。

路径拆分 ​

python
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))

这些方法分别得到目录部分、最后一段名称,以及文件名与扩展名:

text
course/notes
chapter.md
('course/notes/chapter', '.md')

实际路径分隔符会随操作系统变化。

路径状态判断 ​

python
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():

os.walk 递归遍历目录

python
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() 递归遍历,使用相对路径整理输出,并分别累计目录和文件数量。

准备下面的示例目录:

text
01-directory-inventory/
├── main.py
└── sample_files/
    ├── data/
    │   └── scores.csv
    ├── python/
    │   └── demo.py
    └── readme.txt

按目录树创建文件。readme.txt 写入 目录遍历示例文件。,另外两个文件的内容如下:

text
name,score
张三,90
李四,85
python
print("目录遍历示例")

程序根据 main.py 所在位置找到 sample_files,所以无论从哪个工作目录启动,都能定位到示例数据:

python
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}")

运行结果:

text
[.]
- readme.txt
[data]
- scores.csv
[python]
- demo.py
目录数量:2
文件数量:3

案例只读取目录,没有修改或删除文件,适合反复运行。

文件系统安全边界 ​

执行创建、改名或删除操作前,至少确认下面几件事:

  1. 当前工作目录是不是预期目录。
  2. 相对路径最终会指向哪里。
  3. 删除目标是文件、空目录还是包含内容的目录。
  4. 目标是否限制在专门的演示或工作目录中。
  5. 文件系统发生变化时,代码是否能正确处理异常。

基础阶段不要对用户主目录、课程根目录或来源不明的路径执行批量删除。

路径与文件操作分工 ​

模块本课程中的主要职责
os当前工作目录、基础文件操作、目录遍历
os.path阅读传统字符串路径代码
pathlib新项目中的路径表达、拼接和判断
shutil复制、移动和递归删除

掌握 os.path 是为了能够维护已有代码。编写新项目时,后面学习的 pathlib 通常会让路径操作更清晰。

小结 ​

  • 相对路径默认从当前工作目录开始,不是从当前 Python 文件所在目录开始。
  • __file__ 不保证是绝对路径,可以使用 os.path.abspath(__file__) 转换。
  • listdir() 查看一层目录,walk() 可以递归遍历任意深度。
  • mkdir() 创建一级目录,makedirs() 可以创建多级目录。
  • rename()、remove() 和 rmdir() 会直接修改文件系统,运行前必须确认目标。
  • os.path 可以拼接、拆分并判断字符串路径。
  • 新项目中的路径处理会在后续章节优先使用 pathlib。

练习 ​

  1. 使用 os.getcwd() 输出当前工作目录,再把一个相对路径转换成绝对路径。
  2. 使用 os.listdir() 分别打印指定目录中的文件和子目录。
  3. 在 TemporaryDirectory() 中创建、重命名并删除一个测试文件。
  4. 使用 os.path 拆分一个路径,输出目录、文件名和扩展名。
  5. 修改综合案例,分别统计 .py、.txt 和 .csv 文件的数量。