Skip to content

pathlib 路径操作 ​

前面的 os.path 使用字符串表示路径。面对跨平台的路径拼接、目录遍历和文件读写,如何让路径表达更清晰并减少手动处理分隔符?pathlib 把路径封装成对象,可以通过属性和方法完成这些操作。

python
from pathlib import Path

data_file = Path("data") / "student.txt"
print(data_file)

运算符 / 在这里表示拼接路径。Path 会根据当前操作系统使用正确的路径规则,不需要手动拼接 / 或 \\。

编写新项目时,优先考虑 pathlib;阅读已有项目时,仍然需要看懂 os.path。

路径对象类型 ​

pathlib 中的路径类可以分成两类:

pathlib 路径类关系

  • PurePath 只计算路径,不访问文件系统。
  • Path 除了计算路径,还可以判断、创建、遍历和读写文件。
  • Path 会根据当前系统使用 PosixPath 或 WindowsPath。

平时直接使用 Path 即可,不需要主动创建 PosixPath 或 WindowsPath。

跨平台路径分析 ​

如果只想分析另一种操作系统的路径格式,可以使用纯路径类:

python
from pathlib import PurePosixPath, PureWindowsPath

windows_path = PureWindowsPath(r"C:\Users\student\notes.txt")
posix_path = PurePosixPath("/home/student/notes.txt")

print(windows_path.name)
print(windows_path.parent)
print(posix_path.name)
print(posix_path.parent)

PureWindowsPath 可以在 Linux 或 macOS 上正确分析 Windows 路径,因为它只处理路径语法,不检查文件是否真实存在。

不要在非 Windows 系统上使用普通的 Path 分析固定 Windows 路径。此时反斜杠可能被当作文件名中的普通字符。

路径基准与对象创建 ​

工作目录与脚本目录 ​

工作目录与用户目录:

python
from pathlib import Path

print(Path.cwd())
print(Path.home())
  • Path.cwd() 返回当前工作目录。
  • Path.home() 返回当前用户的主目录。

相对路径默认从当前工作目录开始解析。程序从不同目录启动时,Path.cwd() 可能不同。

脚本目录定位:

__file__ 表示当前 Python 文件的路径。可以配合 resolve() 得到明确路径:

python
from pathlib import Path

script_path = Path(__file__).resolve()
script_dir = script_path.parent

print(script_path)
print(script_dir)

需要定位脚本旁边的资源时,通常从 script_dir 继续拼接:

python
data_file = script_dir / "data" / "student.txt"

这样不会依赖程序从哪个工作目录启动。

资源定位对比:当前工作目录 vs 脚本自身目录

路径操作 ​

路径拼接与拆分 ​

路径拼接:

Path 使用 / 拼接多个路径片段:

python
from pathlib import Path

project_dir = Path("course")
notes_dir = project_dir / "notes"
file_path = notes_dir / "chapter.md"

print(project_dir)
print(notes_dir)
print(file_path)

/ 只是生成新的路径对象,不会立即创建目录或文件。

如果后续接口要求字符串,可以使用 str():

python
path_text = str(file_path)

不过 open()、os 和 shutil 中的大部分现代接口都能直接接收 Path,通常不需要提前转换。

路径拆分:

路径对象提供了一组直观属性:

python
from pathlib import Path

file_path = Path("course") / "assets" / "archive.tar.gz"

print(file_path.name)
print(file_path.stem)
print(file_path.suffix)
print(file_path.suffixes)
print(file_path.parent)
print(file_path.parts)
属性结果含义
name完整文件名 archive.tar.gz
stem去掉最后一个扩展名后的名称 archive.tar
suffix最后一个扩展名 .gz
suffixes所有扩展名 ['.tar', '.gz']
parent父路径
parts按当前系统规则拆开的各段路径

Path 路径对象属性解剖全景图

路径名称变换 ​

路径名称变换:

路径对象本身不会被修改。with_name() 和 with_suffix() 会返回新的路径:

python
from pathlib import Path

source_path = Path("reports") / "score.txt"
renamed_path = source_path.with_name("final_score.txt")
markdown_path = renamed_path.with_suffix(".md")

print(source_path)
print(renamed_path)
print(markdown_path)

这些方法只修改路径表达,不会重命名磁盘上的真实文件。

Path 路径变换不可变性与物理重命名对比

路径解析与状态判断 ​

绝对路径解析:

resolve() 会把路径转换成绝对形式,并处理其中的 . 和 ..:

python
from pathlib import Path

target_path = Path("data") / ".." / "README.md"
print(target_path.resolve())

默认情况下,即使目标不存在,也可以计算路径。传入 strict=True 时,目标不存在会抛出 FileNotFoundError:

python
target_path.resolve(strict=True)

相对路径提取:

relative_to() 可以得到一个路径相对于某个父路径的部分:

python
from pathlib import Path

project_dir = Path("/course")
file_path = project_dir / "notes" / "chapter.md"

print(file_path.relative_to(project_dir))

运行结果:

text
notes/chapter.md

如果目标不在指定父路径之下,relative_to() 会抛出 ValueError。实际分隔符会随操作系统变化。

路径标尺运算:resolve 规范化与 relative_to 基准剥离

路径状态判断:

Path 可以直接判断对应的文件系统对象:

python
from pathlib import Path

target_path = Path("data") / "student.txt"

print(target_path.exists())
print(target_path.is_file())
print(target_path.is_dir())
方法判断内容
exists()路径是否存在
is_file()路径是否存在并且是文件
is_dir()路径是否存在并且是目录

判断结果只表示调用时的状态。执行后续文件操作时,仍然需要处理路径可能发生变化的情况。

文件系统操作 ​

目录创建与文本读写 ​

下面在自动清理的临时目录中演示文件操作,不会污染课程目录:

python
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as temp_dir:
    workspace = Path(temp_dir)
    notes_dir = workspace / "data" / "notes"
    notes_dir.mkdir(parents=True, exist_ok=True)

    note_file = notes_dir / "readme.txt"
    note_file.write_text("Path 对象读写文本", encoding="utf-8")

    content = note_file.read_text(encoding="utf-8")
    print(content)

其中:

  • parents=True 允许同时创建缺少的上级目录。
  • exist_ok=True 允许目标目录已经存在。
  • write_text() 和 read_text() 适合一次读写规模较小的文本。
  • 文本读写仍然应该明确指定 UTF-8 编码。

目录安全创建机制与极简文本读写流程

unlink() 可以删除文件,rmdir() 可以删除空目录。这些操作会直接修改文件系统,本章只要求认识;递归删除留到下一章 shutil。

单层遍历与递归遍历 ​

单层遍历:iterdir()

iterdir() 返回当前目录中的直接子项:

python
from pathlib import Path

base_dir = Path(__file__).resolve().parent

for item in sorted(base_dir.iterdir()):
    item_type = "目录" if item.is_dir() else "文件"
    print(f"{item_type}:{item.name}")

它不会自动进入子目录。

递归遍历:Path.walk()

从 Python 3.12 起提供的 Path.walk(),在本课程使用的 Python 3.14 中也可以遍历任意深度的目录:

python
from pathlib import Path

base_dir = Path(__file__).resolve().parent / "sample_files"

for current_dir, dir_names, file_names in base_dir.walk():
    print(f"当前目录:{current_dir}")
    print(f"子目录:{dir_names}")
    print(f"文件:{file_names}")

它与前面学习的 os.walk() 作用相近,但返回的 current_dir 是 Path 对象,可以继续使用 /、relative_to() 等路径操作。

Path.glob() 和 Path.rglob() 可以按照通配符筛选路径。具体通配符规则将在后面的 glob 章节学习。

目录遍历模式对比:单层遍历 vs 递归下潜

案例:生成文件报告 ​

需求: 递归扫描示例目录,输出每个文件的相对路径,并按扩展名统计文件数量;程序只读取文件信息,不修改目录。

功能分析: 以脚本目录为路径基准,使用 Path.walk() 遍历,使用 relative_to() 生成报告路径,使用 suffix 汇总扩展名。

准备下面的示例目录:

text
01-file-report/
├── main.py
└── sample_files/
    ├── data/
    │   └── scores.csv
    ├── notes/
    │   └── lesson.md
    └── readme.txt

按目录树创建三个样本文件,内容如下:

text
pathlib 文件报告案例。
text
name,score
张三,90
李四,85
text
# pathlib 练习资料

使用 Path 对象管理路径。

程序递归遍历目录,输出相对路径并统计扩展名:

python
from pathlib import Path


script_dir = Path(__file__).resolve().parent
base_dir = script_dir / "sample_files"

file_count = 0
suffix_counts = {}

for current_dir, dir_names, file_names in base_dir.walk():
    dir_names.sort()
    file_names.sort()

    for filename in file_names:
        file_path = current_dir / filename
        relative_path = file_path.relative_to(base_dir)
        suffix = file_path.suffix.lower() or "无扩展名"

        file_count += 1
        suffix_counts[suffix] = suffix_counts.get(suffix, 0) + 1

        print(relative_path.as_posix())

print(f"文件数量:{file_count}")
print("扩展名统计:")
for suffix, count in sorted(suffix_counts.items()):
    print(f"{suffix}: {count}")

运行结果:

text
readme.txt
data/scores.csv
notes/lesson.md
文件数量:3
扩展名统计:
.csv: 1
.md: 1
.txt: 1

文件扫描报告器全景流水线

as_posix() 只用于让报告统一显示 /,不改变真实路径。案例只读取文件信息,不会修改示例目录。

模块协作边界 ​

模块主要职责
pathlib表达、拼接、判断和遍历路径
os当前工作目录、环境和底层系统操作
shutil复制、移动和递归删除
glob使用通配符批量匹配路径

大多数标准库接口可以直接接收 Path:

python
from pathlib import Path
import shutil

source_path = Path("data") / "source.txt"
target_path = Path("backup") / "source.txt"

shutil.copy(source_path, target_path)

这段代码只说明接口如何配合。源文件和目标目录不存在时会报错,不能脱离准备好的示例环境直接运行。

小结 ​

  • PurePath 只计算路径,Path 还可以访问文件系统。
  • Path 会根据当前系统选择 Windows 或 POSIX 路径规则。
  • Path(__file__).resolve().parent 可以稳定定位当前脚本目录。
  • / 用于拼接路径,路径属性用于拆分路径。
  • with_name() 和 with_suffix() 只生成新路径,不会修改真实文件。
  • mkdir()、read_text() 和 write_text() 可以直接操作文件系统。
  • iterdir() 查看一层目录,Path.walk()(Python 3.12 起提供)可以递归遍历。
  • Path 可以直接传给 open()、os 和 shutil 等接口。

练习 ​

  1. 分别使用 PureWindowsPath 和 PurePosixPath 拆分两种系统的路径。
  2. 使用 Path(__file__).resolve().parent 找到当前脚本目录,再拼接一个 data 目录。
  3. 使用 with_name() 和 with_suffix() 生成新的文件路径。
  4. 在 TemporaryDirectory() 中创建目录,并使用 write_text()、read_text() 读写中文。
  5. 使用 iterdir() 输出某个目录的直接子项。
  6. 修改综合案例,增加文件总大小统计。