> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# pathlib 完全指南
- URL: https://blog.vercanti.com/pathlib-wan-quan-zhi-nan/
- Published: 2026-08-28T14:34:34.000Z
- Updated: 2026-08-28T14:56:51.000Z
- Description: pathlib 是 Python 3.4 引入的标准库模块，提供面向对象的文件系统路径操作。相比 os.path 的字符串拼接方式，pathlib 的 Path 对象更直观、更安全，跨平台兼容性更好。 Path() 构造函数的参数： pathlib 重载了 / 运算符来进行路径拼接，这是最推荐的方式： joinpath 的参数： with_name 的参数： with_suffix 的参数： write_text 的参数： read_text 的参数： write_bytes 的参数： read_bytes 无参数，返回 bytes。 Path.open
- Author: yellowdog
- Tags: Python, 基础

> 官方文档：<https://docs.python.org/3/library/pathlib.html>  
> 适用版本：Python 3.12（2026-05-07 核实）

`pathlib` 是 Python 3.4 引入的标准库模块，提供面向对象的文件系统路径操作。相比 `os.path` 的字符串拼接方式，`pathlib` 的 `Path` 对象更直观、更安全，跨平台兼容性更好。

## Path 对象创建

### 基本构造

```python
from pathlib import Path

# 从字符串创建
p = Path("/home/user/documents")
p = Path("relative/path/to/file.txt")

# Windows 风格（反斜杠）也被支持
p = Path(r"C:\Users\user\documents")

# 传入多个部分，自动拼接
p = Path("/home", "user", "documents", "file.txt")
# 等同于 Path("/home/user/documents/file.txt")

```

`Path()` 构造函数的参数：

| 参数             | 类型         | 默认值      | 说明              |
| -------------- | ---------- | -------- | --------------- |
| \*pathsegments | str 或 Path | 必填（至少一个） | 路径片段，自动用系统分隔符连接 |

### 特殊构造方法

```python
from pathlib import Path

# 当前工作目录
cwd = Path.cwd()          # 等同于 os.getcwd()

# 用户主目录
home = Path.home()        # 等同于 Path(os.path.expanduser("~"))

# 从环境变量构建
import os
config_dir = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))

```

### 平台特定的 Path 子类

```python
from pathlib import PurePosixPath, PureWindowsPath, PosixPath, WindowsPath

# 纯路径（只做字符串操作，不访问文件系统）
posix = PurePosixPath("/home/user/file.txt")
win = PureWindowsPath(r"C:\Users\user\file.txt")

# 具体路径（实际访问文件系统）
# 在 Linux/macOS 上：Path() 返回 PosixPath
# 在 Windows 上：Path() 返回 WindowsPath

```

## 路径拼接

### `/` 运算符

`pathlib` 重载了 `/` 运算符来进行路径拼接，这是最推荐的方式：

```python
from pathlib import Path

base = Path("/home/user")

# Path / str
p = base / "documents" / "file.txt"
# PosixPath('/home/user/documents/file.txt')

# Path / Path
sub = Path("documents")
p = base / sub / "file.txt"

# str / Path 也可以（左侧是字符串）
p = "/home/user" / Path("file.txt")

# 警告：如果右侧是绝对路径，会丢弃左侧
p = base / "/etc/passwd"
# 结果是 PosixPath('/etc/passwd')，而非预期的拼接

```

### `joinpath()` 方法

```python
from pathlib import Path

p = Path("/home/user").joinpath("documents", "file.txt")
# 等同于 Path("/home/user") / "documents" / "file.txt"

```

`joinpath` 的参数：

| 参数             | 类型         | 默认值 | 说明             |
| -------------- | ---------- | --- | -------------- |
| \*pathsegments | str 或 Path | 必填  | 要连接的路径片段，可传入多个 |

## 路径分解

```python
from pathlib import Path

p = Path("/home/user/documents/report.final.pdf")

p.parent        # PosixPath('/home/user/documents')
p.parents[0]    # PosixPath('/home/user/documents')
p.parents[1]    # PosixPath('/home/user')
p.parents[2]    # PosixPath('/home')
p.name          # 'report.final.pdf'（含扩展名的文件名）
p.stem          # 'report.final'（不含最后一个扩展名）
p.suffix        # '.pdf'（最后一个扩展名）
p.suffixes      # ['.final', '.pdf']（所有扩展名）
p.parts         # ('/', 'home', 'user', 'documents', 'report.final.pdf')
p.root          # '/'（根路径）
p.anchor        # '/'（根 + 盘符，Windows 上为 'C:\\'）
p.drive         # ''（Windows 上为 'C:'）

```

### 修改路径组成部分

```python
from pathlib import Path

p = Path("/home/user/documents/report.pdf")

# 更换文件名
p.with_name("summary.pdf")
# PosixPath('/home/user/documents/summary.pdf')

# 更换扩展名
p.with_suffix(".txt")
# PosixPath('/home/user/documents/report.txt')

# 去掉扩展名
p.with_suffix("")
# PosixPath('/home/user/documents/report')

# 更换父目录（Python 3.12+）
p.with_segments("/tmp", p.name)
# PosixPath('/tmp/report.pdf')

```

`with_name` 的参数：

| 参数   | 类型  | 默认值 | 说明                    |
| ---- | --- | --- | --------------------- |
| name | str | 必填  | 新的文件名（含扩展名），不能包含路径分隔符 |

`with_suffix` 的参数：

| 参数     | 类型  | 默认值 | 说明                          |
| ------ | --- | --- | --------------------------- |
| suffix | str | 必填  | 新的扩展名（需以 . 开头），传入 "" 则去掉扩展名 |

## 文件读写操作

### 文本读写

```python
from pathlib import Path

p = Path("data.txt")

# 写入文本
p.write_text("Hello, World!\n第二行", encoding="utf-8")

# 读取文本
content = p.read_text(encoding="utf-8")

```

`write_text` 的参数：

| 参数       | 类型         | 默认值        | 说明                             |
| -------- | ---------- | ---------- | ------------------------------ |
| data     | str        | 必填         | 要写入的字符串内容                      |
| encoding | str 或 None | None（系统默认） | 编码格式，建议显式指定 "utf-8"            |
| errors   | str 或 None | None       | 编码错误处理策略，如 "replace", "ignore" |
| newline  | str 或 None | None       | 换行符模式，与 open() 的 newline 参数相同  |

`read_text` 的参数：

| 参数       | 类型         | 默认值                | 说明          |
| -------- | ---------- | ------------------ | ----------- |
| encoding | str 或 None | None（系统默认）         | 编码格式，建议显式指定 |
| errors   | str 或 None | None               | 编码错误处理策略    |
| newline  | str 或 None | None（Python 3.13+） | 换行符模式       |

### 二进制读写

```python
from pathlib import Path

p = Path("data.bin")

# 写入二进制
p.write_bytes(b"\x00\x01\x02\x03")

# 读取二进制
data = p.read_bytes()

```

`write_bytes` 的参数：

| 参数   | 类型           | 默认值 | 说明        |
| ---- | ------------ | --- | --------- |
| data | bytes 或类字节类型 | 必填  | 要写入的二进制数据 |

`read_bytes` 无参数，返回 `bytes`。

### 使用 `open()` 打开文件

```python
from pathlib import Path

p = Path("data.txt")

# 与内置 open() 用法相同，返回文件对象
with p.open("r", encoding="utf-8") as f:
    content = f.read()

with p.open("w", encoding="utf-8") as f:
    f.write("新内容")

# 追加模式
with p.open("a", encoding="utf-8") as f:
    f.write("\n追加内容")

```

`Path.open()` 的参数：

| 参数        | 类型         | 默认值  | 说明                 |
| --------- | ---------- | ---- | ------------------ |
| mode      | str        | "r"  | 打开模式，与内置 open() 相同 |
| buffering | int        | \-1  | 缓冲策略               |
| encoding  | str 或 None | None | 文本模式的编码            |
| errors    | str 或 None | None | 编码错误处理             |
| newline   | str 或 None | None | 换行符处理              |

## 目录操作

### 创建目录

```python
from pathlib import Path

p = Path("/home/user/new_dir")

# 创建单级目录
p.mkdir()

# 创建多级目录（parents=True 自动创建父目录）
deep = Path("/home/user/a/b/c")
deep.mkdir(parents=True, exist_ok=True)

```

`mkdir` 的参数：

| 参数        | 类型   | 默认值   | 说明                   |
| --------- | ---- | ----- | -------------------- |
| mode      | int  | 0o777 | 权限位（Unix），受 umask 影响 |
| parents   | bool | False | 为 True 时自动创建所有父目录    |
| exist\_ok | bool | False | 为 True 时若目录已存在不抛异常   |

### 删除目录

```python
from pathlib import Path

# 只能删除空目录
p = Path("/home/user/empty_dir")
p.rmdir()

# 删除非空目录需要 shutil
import shutil
shutil.rmtree("/home/user/full_dir")

```

### 遍历目录

```python
from pathlib import Path

p = Path("/home/user/documents")

# iterdir()：遍历直接子项（不递归）
for item in p.iterdir():
    if item.is_file():
        print(f"文件: {item.name}")
    elif item.is_dir():
        print(f"目录: {item.name}")

```

`iterdir` 无参数，返回一个生成器，每次产出一个 `Path` 对象。遍历顺序不保证。

### glob 模式匹配

```python
from pathlib import Path

p = Path("/home/user/documents")

# glob：在当前目录下匹配（支持 * 和 ?）
for txt_file in p.glob("*.txt"):
    print(txt_file)

# 匹配直接子目录下的所有 Python 文件
for py_file in p.glob("*/*.py"):
    print(py_file)

# rglob：递归匹配所有层级（相当于 glob("**/*.txt")）
for txt_file in p.rglob("*.txt"):
    print(txt_file)

# 只匹配目录
for d in p.glob("*/"):
    print(d)

```

`glob` 的参数：

| 参数              | 类型          | 默认值        | 说明                                 |
| --------------- | ----------- | ---------- | ---------------------------------- |
| pattern         | str         | 必填         | glob 模式，\* 匹配任意文件名字符，\*\* 匹配任意层级目录 |
| case\_sensitive | bool 或 None | None（跟随系统） | 是否区分大小写（Python 3.12+）              |

`rglob` 的参数：

| 参数              | 类型          | 默认值  | 说明                      |
| --------------- | ----------- | ---- | ----------------------- |
| pattern         | str         | 必填   | glob 模式，自动在前面加 \*\*/ 前缀 |
| case\_sensitive | bool 或 None | None | 是否区分大小写（Python 3.12+）   |

## 路径检查

```python
from pathlib import Path

p = Path("/home/user/file.txt")

p.exists()      # 路径是否存在（文件或目录）
p.is_file()     # 是否是普通文件
p.is_dir()      # 是否是目录
p.is_symlink()  # 是否是符号链接
p.is_absolute() # 是否是绝对路径
p.is_relative_to("/home")  # 是否相对于给定路径（Python 3.9+）

```

### `stat()` 获取文件元信息

```python
from pathlib import Path
import datetime

p = Path("file.txt")
info = p.stat()

info.st_size    # 文件大小（字节）
info.st_mtime   # 最后修改时间（Unix 时间戳）
info.st_ctime   # 创建时间（Windows）/ inode 变更时间（Unix）
info.st_mode    # 文件权限和类型

# 转换时间戳为 datetime
mtime = datetime.datetime.fromtimestamp(info.st_mtime)
print(f"最后修改: {mtime}")

```

`stat` 无参数，返回 `os.stat_result` 对象。若路径不存在则抛出 `FileNotFoundError`。

## 文件重命名、移动与删除

### `rename` 重命名 / 移动

```python
from pathlib import Path

p = Path("old_name.txt")

# 重命名（返回新路径的 Path 对象）
new_p = p.rename("new_name.txt")

# 移动到其他目录
new_p = p.rename("/home/user/documents/old_name.txt")

```

`rename` 的参数：

| 参数     | 类型         | 默认值 | 说明                 |
| ------ | ---------- | --- | ------------------ |
| target | str 或 Path | 必填  | 目标路径，如目标已存在行为取决于平台 |

### `replace` 强制替换

```python
from pathlib import Path

# replace 会强制覆盖目标文件（原子操作）
p = Path("source.txt")
p.replace("destination.txt")  # 即使 destination.txt 存在也会覆盖

```

`replace` 的参数：

| 参数     | 类型         | 默认值 | 说明              |
| ------ | ---------- | --- | --------------- |
| target | str 或 Path | 必填  | 目标路径，若存在则原子性地替换 |

### `unlink` 删除文件

```python
from pathlib import Path

p = Path("file.txt")
p.unlink()                    # 文件不存在时抛出 FileNotFoundError
p.unlink(missing_ok=True)     # 文件不存在时静默忽略（Python 3.8+）

```

`unlink` 的参数：

| 参数          | 类型   | 默认值   | 说明                 |
| ----------- | ---- | ----- | ------------------ |
| missing\_ok | bool | False | 为 True 时若文件不存在不抛异常 |

### `symlink_to` 创建符号链接

```python
from pathlib import Path

link = Path("/home/user/link.txt")
link.symlink_to("/home/user/original.txt")

# 读取符号链接目标
target = link.resolve()       # 解析为绝对路径
target = link.readlink()      # 读取链接目标（Python 3.9+）

```

`symlink_to` 的参数：

| 参数                    | 类型         | 默认值   | 说明                         |
| --------------------- | ---------- | ----- | -------------------------- |
| target                | str 或 Path | 必填    | 符号链接指向的目标路径                |
| target\_is\_directory | bool       | False | Windows 上创建目录符号链接时需设为 True |

## 路径转换

```python
from pathlib import Path

p = Path("/home/user/file.txt")

# 转为字符串
str(p)                # '/home/user/file.txt'
p.as_posix()          # '/home/user/file.txt'（强制 POSIX 格式）

# 转为绝对路径（不解析符号链接）
p.absolute()

# 解析为规范绝对路径（解析符号链接和 .., .）
p.resolve()

# 计算相对路径
p.relative_to("/home/user")  # PosixPath('file.txt')

# 转为 URI
p.as_uri()            # 'file:///home/user/file.txt'

```

`resolve` 的参数：

| 参数     | 类型   | 默认值   | 说明                                |
| ------ | ---- | ----- | --------------------------------- |
| strict | bool | False | 为 True 时若路径不存在抛 FileNotFoundError |

`relative_to` 的参数：

| 参数       | 类型         | 默认值   | 说明                                |
| -------- | ---------- | ----- | --------------------------------- |
| other    | str 或 Path | 必填    | 基准路径，若当前路径不在其下抛 ValueError        |
| walk\_up | bool       | False | 为 True 时允许使用 .. 向上跳（Python 3.12+） |

## 与 `os.path` 的对比迁移表

| os.path 写法                     | pathlib 写法                                  |
| ------------------------------ | ------------------------------------------- |
| os.path.join(a, b)             | Path(a) / b                                 |
| os.path.abspath(p)             | Path(p).resolve()                           |
| os.path.dirname(p)             | Path(p).parent                              |
| os.path.basename(p)            | Path(p).name                                |
| os.path.splitext(p)\[0\]       | Path(p).stem                                |
| os.path.splitext(p)\[1\]       | Path(p).suffix                              |
| os.path.exists(p)              | Path(p).exists()                            |
| os.path.isfile(p)              | Path(p).is\_file()                          |
| os.path.isdir(p)               | Path(p).is\_dir()                           |
| os.path.expanduser("\~")       | Path.home()                                 |
| os.getcwd()                    | Path.cwd()                                  |
| os.rename(src, dst)            | Path(src).rename(dst)                       |
| os.remove(p)                   | Path(p).unlink()                            |
| os.rmdir(p)                    | Path(p).rmdir()                             |
| os.makedirs(p, exist\_ok=True) | Path(p).mkdir(parents=True, exist\_ok=True) |
| glob.glob("\*.txt")            | Path(".").glob("\*.txt")                    |
| open(p, "r")                   | Path(p).open("r") 或 Path(p).read\_text()    |

### 与 `os` 函数的互操作

很多接受 `str` 路径的 `os` 函数也接受 `Path` 对象（Python 3.6+ 通过 `os.fspath` 协议）：

```python
import os
from pathlib import Path

p = Path("/home/user/file.txt")

os.stat(p)          # 直接传 Path 对象
os.chmod(p, 0o644)
os.environ["PATH"]  # 环境变量仍是字符串

# 需要字符串时用 str() 或 os.fspath()
os.system(f"cat {p}")      # f-string 中 Path 自动转字符串
subprocess.run(["cat", str(p)])

```

## 最佳实践

### 1\. 始终显式指定编码

```python
from pathlib import Path

# 不好：依赖系统默认编码，在不同系统上行为不一致
content = Path("file.txt").read_text()

# 好：显式指定 UTF-8
content = Path("file.txt").read_text(encoding="utf-8")

```

### 2\. 用 `with_suffix` 安全地修改扩展名

```python
from pathlib import Path

def convert_path(src: Path, new_suffix: str) -> Path:
    return src.with_suffix(new_suffix)

# 批量转换
src_dir = Path("input")
dst_dir = Path("output")
dst_dir.mkdir(exist_ok=True)

for csv_file in src_dir.glob("*.csv"):
    dst_file = dst_dir / csv_file.with_suffix(".parquet").name
    process(csv_file, dst_file)

```

### 3\. 用 `resolve()` 消除路径歧义

```python
from pathlib import Path

def safe_open(base_dir: Path, user_input: str) -> str:
    # 防止路径遍历攻击（../../etc/passwd）
    target = (base_dir / user_input).resolve()
    base_dir = base_dir.resolve()
    if not target.is_relative_to(base_dir):
        raise PermissionError(f"禁止访问 base_dir 之外的路径: {target}")
    return target.read_text(encoding="utf-8")

```

### 4\. 处理大量文件时用生成器

```python
from pathlib import Path

# glob 和 rglob 返回生成器，不会一次性加载所有路径到内存
def count_lines(directory: Path) -> int:
    total = 0
    for py_file in directory.rglob("*.py"):
        try:
            total += len(py_file.read_text(encoding="utf-8").splitlines())
        except (PermissionError, UnicodeDecodeError):
            continue
    return total

```

## 踩坑与注意事项

### 踩坑 1：Windows 路径分隔符

```python
from pathlib import Path

# 在 Windows 上，Path 使用反斜杠，but as_posix() 转换为正斜杠
p = Path("C:/Users/user/file.txt")   # 正斜杠在 Windows 上也可以
p = Path(r"C:\Users\user\file.txt")  # 反斜杠（raw string）

str(p)        # 'C:\\Users\\user\\file.txt'（Windows 上）
p.as_posix()  # 'C:/Users/user/file.txt'（跨平台字符串）

# 不要用普通字符串拼接路径！
bad = "C:\users\new_file.txt"   # \n 和 \u 会被解释为转义字符
good = Path(r"C:\users\new_file.txt")

```

### 踩坑 2：`mkdir` 的 `parents` 和 `exist_ok` 参数缺失

```python
from pathlib import Path

# 错误：父目录不存在时抛 FileNotFoundError
Path("/tmp/a/b/c").mkdir()

# 错误：目录已存在时抛 FileExistsError
Path("/tmp/existing").mkdir()

# 正确：几乎总是应该使用这两个参数
Path("/tmp/a/b/c").mkdir(parents=True, exist_ok=True)

```

### 踩坑 3：`rename` 的跨设备移动限制

```python
from pathlib import Path
import shutil

src = Path("/tmp/file.txt")

# 错误：跨设备（跨磁盘）rename 会抛 OSError
src.rename("/mnt/other_disk/file.txt")

# 正确：跨设备移动用 shutil.move
shutil.move(str(src), "/mnt/other_disk/file.txt")

```

### 踩坑 4：`glob` 的大小写敏感性

```python
from pathlib import Path

# 在 Windows（不区分大小写的文件系统）上：
p = Path("C:/Users")
list(p.glob("*.TXT"))  # 可能匹配 .txt 文件，也可能不匹配，取决于 Python 版本

# Python 3.12+ 用 case_sensitive 参数明确指定
list(p.glob("*.txt", case_sensitive=False))  # 总是不区分大小写

```

### 踩坑 5：`iterdir` 不排序

```python
from pathlib import Path

# iterdir 的顺序是文件系统顺序，不是字母序
p = Path(".")
files = list(p.iterdir())   # 顺序不确定

# 需要排序时：
files = sorted(p.iterdir())                           # 按文件名排序
files = sorted(p.iterdir(), key=lambda f: f.stat().st_mtime)  # 按修改时间

```

### 踩坑 6：`resolve()` 在路径不存在时的行为

```python
from pathlib import Path

# Python 3.6+ 默认 strict=False：即使路径不存在也不报错
p = Path("/nonexistent/path/file.txt").resolve()

# 需要确保路径存在时使用 strict=True
try:
    p = Path("/nonexistent/path").resolve(strict=True)
except FileNotFoundError:
    print("路径不存在")

```

---

## 常见陷阱

### 陷阱：`Path /` 运算符右侧使用绝对路径会丢弃左侧

**现象：** `Path('/home/user') / '/etc/passwd'` 结果是 `PosixPath('/etc/passwd')`，左侧路径被忽略。  
**原因：** `/` 运算符等价于 `Path.joinpath()`，若右侧是绝对路径，行为与 `os.path.join` 一致——直接返回右侧路径。  
**解决：** 用 `.lstrip('/')` 处理右侧路径，或确保右侧始终是相对路径：

```python
# 意外：结果是 /etc/passwd
Path('/home/user') / '/etc/passwd'

# 正确：用 relative 路径
Path('/home/user') / 'etc/passwd'  # /home/user/etc/passwd

```

### 陷阱：`Path.glob('**/*.py')` 在某些 Python 版本中不递归根目录

**现象：** `Path('.').glob('**/*.py')` 没有返回当前目录下的直接 `.py` 文件，只返回子目录中的。  
**原因：** Python 3.11 之前 `**` 不匹配当前目录自身，只匹配子目录。  
**解决：** 同时使用 `glob('*.py')` 和 `glob('**/*.py')`，或升级到 Python 3.12+（`**` 语义统一）。

### 陷阱：Windows 路径与 POSIX 路径混用导致跨平台失败

**现象：** 代码在 macOS/Linux 正常，Windows 上路径拼接出现反斜杠/正斜杠混用报错。  
**原因：** 硬编码 `/` 分隔符的字符串路径在 Windows 可能不被某些 API 接受，或 `str(path)` 输出反斜杠让字符串处理出错。  
**解决：** 始终用 `Path` 对象操作路径，不手动拼接字符串；需要字符串时用 `path.as_posix()` 获取 POSIX 格式。

---

## 参见

[内置函数完全参考](https://blog.vercanti.com/python-nei-zhi-han-shu-wan-quan-can-kao/)  
[contextlib完全指南](https://blog.vercanti.com/contextlib-wan-quan-zhi-nan/)