← 回到手记
代码

pathlib:别再手写路径拼接了

用标准库 pathlib.Path 替代 os.path 和字符串拼接:/ 运算符、读写文件、mkdir、exists 与常见踩坑,示例在 Python 3.13 验证。

要解决的问题

写 Python 后端脚本时,路径几乎绕不开:配置文件在哪、日志写到哪、上传目录怎么拼、临时文件落在何处。很多人习惯 os.path.join("a", "b", "c"),或者直接 "a" + "/" + "b"。在 Linux 上往往能跑通,一到 Windows、或脚本换了工作目录,就开始出现少斜杠、多斜杠、编码乱码这类问题。

标准库模块 pathlib 从 Python 3.4 起就在了。它把路径当成对象来用:用 / 运算符拼接,用方法读写文本、创建目录、判断文件是否存在。新脚本里优先用 Path,可读性和跨平台都会稳一些。本文示例已在本机 Python 3.13.5 验证;3.12 及以上 同样适用。

核心概念

先记住日常最常用的几个点:

  • from pathlib import Path:多数情况只需要 Path 这一个类。它会实例化当前系统对应的具体路径(POSIX 上是 PosixPath,Windows 上是 WindowsPath)。
  • 拼接:base / "data" / "logs",或 base.joinpath("data", "logs"),语义接近 os.path.join,但不用自己管分隔符。
  • 拆解:.name 是最后一段文件名,.stem 去掉最后后缀后的主名,.suffix 是最后一段后缀(如 .log),.parent 是父目录。
  • 读写:read_text / write_text 一次读写整个文本文件;请始终写上 encoding="utf-8"。
  • 判断:.exists()、.is_file()、.is_dir()。
  • 建目录:mkdir(parents=True, exist_ok=True),效果类似 shell 的 mkdir -p。
  • 绝对化:resolve() 会得到绝对路径,并尽量展开符号链接。
  • 列举:iterdir() 列出直接子项;glob("*.py") 按模式匹配(可用 ** 做递归,见官方文档)。

完整 API 与版本说明见官方文档:pathlib — Object-oriented filesystem paths。设计动机见 PEP 428。

示例代码

下面是一段可直接粘贴的日常写法(思路与本文本地验证一致):

from pathlib import Path

base = Path("/tmp/demo-app")
log_dir = base / "var" / "log"          # 等同 base.joinpath("var", "log")
log_dir.mkdir(parents=True, exist_ok=True)

log = log_dir / "app.log"
log.write_text("你好 pathlib\n", encoding="utf-8")
text = log.read_text(encoding="utf-8")
print(text)  # 你好 pathlib

print(log.name)    # app.log
print(log.stem)    # app
print(log.suffix)  # .log
print(log.parent)  # .../var/log

assert log.exists() and log.is_file()
assert log_dir.is_dir()
print(log.resolve())  # 绝对路径

# 列目录与简单 glob
for child in sorted(log_dir.iterdir()):
    print("child:", child.name)
print(list(log_dir.glob("*.log")))

关于「修改路径」:Path 对象本身是不可变的。写 p /= "subdir" 时,是把变量 p 重新绑定到新的 Path,并不是原地改原来那个对象。需要备份文件名时,不要写 path + ".bak"(会 TypeError),可以:

bak = log.parent / (log.name + ".bak")
# 或
bak = log.with_name(log.name + ".bak")

传给只接受字符串的老接口时,用 str(path) 或 os.fspath(path)。多数接受路径的现代 API 已支持 os.PathLike,可以直接传 Path。

常见误区

  1. 用 + 把 Path 和字符串硬拼
    Path("/tmp/a") + "/b" 会直接类型错误。拼接请用 / 或 joinpath。

  2. 读写文本不指定编码
    encoding 默认跟平台 locale 走。在 Linux 上碰巧是 UTF-8,到 Windows 就可能乱码。后端日志、配置、JSON 旁路文件,一律显式 encoding="utf-8"。

  3. 把相对路径当成「相对脚本」
    Path("data/out.txt") 相对的是进程当前工作目录,不是 .py 文件所在目录。若要相对脚本定位资源,常见写法是:
    Path(__file__).resolve().parent / "data" / "out.txt"。

  4. 忘记少数库只要 str
    报「期望 str」时再转换即可;不要因此退回整段手写字符串路径。官方对比表也说明了 pathlib 与 os.path 的对应关系,见文档中的 Comparison to the os and os.path modules。

  5. 为了「跨平台」自己拼 / 和 \\
    交给 Path:它会按当前系统处理分隔符。需要在 Unix 上操作 Windows 风格路径字符串时,才考虑 PureWindowsPath 这类纯路径类(一般业务代码用不到)。

  6. mkdir 参数没配对
    只写 mkdir() 时,父目录不存在会报错,目录已存在也会报错。一次性建多级且允许已存在,请用 parents=True, exist_ok=True。

小结

新写的 Python 后端脚本,路径处理优先用 pathlib.Path:用 / 拼接,用 read_text/write_text 并固定 UTF-8,用 mkdir(parents=True, exist_ok=True) 建目录,用 exists/is_file/is_dir 判断,需要规范绝对路径时再调用 resolve(),列举用 iterdir/glob。os.path 没有过时,底层或兼容旧代码时仍会用到;但不必再靠字符串加斜杠来拼日常路径。养成「路径是对象」的习惯,跨平台和可读性都会轻松不少。

参考资料

  1. Python 文档:pathlib
  2. PEP 428 — The pathlib module
  3. os.PathLike
  4. pathlib 与 os.path 对照
✳

谢谢你读到这里。