pathlib 路径操作
前面的 os.path 使用字符串表示路径。面对跨平台的路径拼接、目录遍历和文件读写,如何让路径表达更清晰并减少手动处理分隔符?pathlib 把路径封装成对象,可以通过属性和方法完成这些操作。
from pathlib import Path
data_file = Path("data") / "student.txt"
print(data_file)运算符 / 在这里表示拼接路径。Path 会根据当前操作系统使用正确的路径规则,不需要手动拼接 / 或 \\。
编写新项目时,优先考虑 pathlib;阅读已有项目时,仍然需要看懂 os.path。
路径对象类型
pathlib 中的路径类可以分成两类:
PurePath只计算路径,不访问文件系统。Path除了计算路径,还可以判断、创建、遍历和读写文件。Path会根据当前系统使用PosixPath或WindowsPath。
平时直接使用 Path 即可,不需要主动创建 PosixPath 或 WindowsPath。
跨平台路径分析
如果只想分析另一种操作系统的路径格式,可以使用纯路径类:
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 路径。此时反斜杠可能被当作文件名中的普通字符。
路径基准与对象创建
工作目录与脚本目录
工作目录与用户目录:
from pathlib import Path
print(Path.cwd())
print(Path.home())Path.cwd()返回当前工作目录。Path.home()返回当前用户的主目录。
相对路径默认从当前工作目录开始解析。程序从不同目录启动时,Path.cwd() 可能不同。
脚本目录定位:
__file__ 表示当前 Python 文件的路径。可以配合 resolve() 得到明确路径:
from pathlib import Path
script_path = Path(__file__).resolve()
script_dir = script_path.parent
print(script_path)
print(script_dir)需要定位脚本旁边的资源时,通常从 script_dir 继续拼接:
data_file = script_dir / "data" / "student.txt"这样不会依赖程序从哪个工作目录启动。
路径操作
路径拼接与拆分
路径拼接:
Path 使用 / 拼接多个路径片段:
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():
path_text = str(file_path)不过 open()、os 和 shutil 中的大部分现代接口都能直接接收 Path,通常不需要提前转换。
路径拆分:
路径对象提供了一组直观属性:
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 | 按当前系统规则拆开的各段路径 |
路径名称变换
路径名称变换:
路径对象本身不会被修改。with_name() 和 with_suffix() 会返回新的路径:
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)这些方法只修改路径表达,不会重命名磁盘上的真实文件。
路径解析与状态判断
绝对路径解析:
resolve() 会把路径转换成绝对形式,并处理其中的 . 和 ..:
from pathlib import Path
target_path = Path("data") / ".." / "README.md"
print(target_path.resolve())默认情况下,即使目标不存在,也可以计算路径。传入 strict=True 时,目标不存在会抛出 FileNotFoundError:
target_path.resolve(strict=True)相对路径提取:
relative_to() 可以得到一个路径相对于某个父路径的部分:
from pathlib import Path
project_dir = Path("/course")
file_path = project_dir / "notes" / "chapter.md"
print(file_path.relative_to(project_dir))运行结果:
notes/chapter.md如果目标不在指定父路径之下,relative_to() 会抛出 ValueError。实际分隔符会随操作系统变化。
路径状态判断:
Path 可以直接判断对应的文件系统对象:
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() | 路径是否存在并且是目录 |
判断结果只表示调用时的状态。执行后续文件操作时,仍然需要处理路径可能发生变化的情况。
文件系统操作
目录创建与文本读写
下面在自动清理的临时目录中演示文件操作,不会污染课程目录:
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() 返回当前目录中的直接子项:
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 中也可以遍历任意深度的目录:
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 章节学习。
案例:生成文件报告
需求: 递归扫描示例目录,输出每个文件的相对路径,并按扩展名统计文件数量;程序只读取文件信息,不修改目录。
功能分析: 以脚本目录为路径基准,使用 Path.walk() 遍历,使用 relative_to() 生成报告路径,使用 suffix 汇总扩展名。
准备下面的示例目录:
01-file-report/
├── main.py
└── sample_files/
├── data/
│ └── scores.csv
├── notes/
│ └── lesson.md
└── readme.txt按目录树创建三个样本文件,内容如下:
pathlib 文件报告案例。name,score
张三,90
李四,85# pathlib 练习资料
使用 Path 对象管理路径。程序递归遍历目录,输出相对路径并统计扩展名:
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}")运行结果:
readme.txt
data/scores.csv
notes/lesson.md
文件数量:3
扩展名统计:
.csv: 1
.md: 1
.txt: 1as_posix() 只用于让报告统一显示 /,不改变真实路径。案例只读取文件信息,不会修改示例目录。
模块协作边界
| 模块 | 主要职责 |
|---|---|
pathlib | 表达、拼接、判断和遍历路径 |
os | 当前工作目录、环境和底层系统操作 |
shutil | 复制、移动和递归删除 |
glob | 使用通配符批量匹配路径 |
大多数标准库接口可以直接接收 Path:
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等接口。
练习
- 分别使用
PureWindowsPath和PurePosixPath拆分两种系统的路径。 - 使用
Path(__file__).resolve().parent找到当前脚本目录,再拼接一个data目录。 - 使用
with_name()和with_suffix()生成新的文件路径。 - 在
TemporaryDirectory()中创建目录,并使用write_text()、read_text()读写中文。 - 使用
iterdir()输出某个目录的直接子项。 - 修改综合案例,增加文件总大小统计。