用 pathlib 替代 os.path 的 8 个实用场景
痛点:os.path 写多了像在做字符串体操
先看一段真实代码:
import os
base = "/data/report"
path = os.path.join(base, "raw", "2024", "a.csv")
if os.path.exists(os.path.dirname(path)):
name = os.path.splitext(os.path.basename(path))[0]
四行代码,套了三层函数,眼睛得来回跳。问题不在于 os.path 有 bug,而在于它把「路径」当字符串处理:拼接靠函数调用,取父目录靠 dirname,取文件名靠 basename,改后缀靠 splitext 再手动拼回去。字符串拼错了不报错,跑起来才炸。
原理:Path 是对象,不是字符串
pathlib 把路径抽象成对象。Path 是具体实现,会根据系统自动变成 PosixPath 或 WindowsPath,所以同一份代码在 Windows 上分隔符自动是反斜杠。它重载了 / 运算符,Path("a") / "b" 等价于 os.path.join;所有方法都返回新的 Path 对象,原对象不变,链式调用不会互相污染。另外 Path 实现了 __fspath__,可以直接丢给 open()、os.stat()、shutil.copy() 这些老接口,不需要先转字符串。
8 个场景,一行换一行
- 拼接:
Path("data") / "raw" / "a.csv",比os.path.join("data", "raw", "a.csv")短,也不会因为少写一个分隔符出错。 - 拆解:
.name、.stem、.suffix、.parent,对应 basename、去掉后缀的名字、后缀、dirname。 - 判断:
.exists()、.is_file()、.is_dir(),省掉 os.path 前缀。 - 建目录:
.mkdir(parents=True, exist_ok=True)一次搞定 os.makedirs 的递归和已存在报错。 - 遍历:
.glob("*.py")和.rglob("*.log"),后者递归,不用手写 os.walk 循环。 - 读写:
.read_text(encoding="utf-8")、.write_text(...),不用 with open 三行。 - 绝对路径:
.resolve()顶替 os.path.abspath 加 realpath,顺便解掉..和符号链接。 - 改后缀:
.with_suffix(".json")、.with_name("new.txt"),不用 splitext 再字符串相加。
可运行示例
from pathlib import Path
root = Path("demo_out")
root.mkdir(parents=True, exist_ok=True)
log = root / "logs" / "app.log"
log.parent.mkdir(parents=True, exist_ok=True)
log.write_text("hello\nworld\n", encoding="utf-8")
print(log.name, log.stem, log.suffix) # app.log app .log
print(log.exists(), log.is_file()) # True True
print(log.read_text(encoding="utf-8").splitlines()) # ['hello', 'world']
print(log.with_suffix(".json").name) # app.json
print([p.name for p in root.rglob("*.log")]) # ['app.log']
print(log.resolve().is_absolute()) # True
跑完会在当前目录生成 demo_out/logs/app.log,输出和注释一致。
几个容易踩的坑
第一,Path 不能和字符串相加。Path("a") + "/b" 直接 TypeError,要拼就继续用 /,要转字符串用 str(p) 或 f-string。
第二,Path("a") / "/b" 结果是 /b,因为右边是绝对路径会覆盖左边,和 os.path.join 行为一致,但很容易在变量拼接时忽略。
第三,相对路径是相对当前工作目录的,不是相对脚本文件。脚本里 Path("config.yaml") 取决于你在哪运行,用 Path(__file__).parent / "config.yaml" 才稳。
第四,.glob("*") 会带出隐藏文件,而 glob 模块的 glob.glob("*") 默认不会,两套 API 行为不一致,迁移时注意。
第五,.resolve() 默认不要求路径存在,路径不存在也能返回绝对路径,但涉及符号链接时语义和老代码的 realpath 有细微差别,最好显式测一遍。
小结
os.path 没被废弃,处理纯字符串路径、和老代码兼容时照样能用。但只要路径要参与判断、遍历、读写,Path 对象的链式写法更短也更难写错。建议新代码一律 from pathlib import Path,老代码碰到就顺手换,一次换一个函数,风险很低。
- 点赞
- 收藏
- 关注作者
评论(0)