小白学 Python
AI 编程指南GitHub
© 2026 小白学 Python · 基于 walter201230/Python 教程
课程目录关于本站联系方式隐私政策GitHub

pathlib:现代化路径处理

完整讲解 · 5 段教学·配 4 道练习题·预计 30 分钟

本页是本章的通读版,可直接读完全部讲解。想动手写代码、跑判分,去 闯关模式。

教学 01 / 05

第十八节:pathlib——现代化路径处理

先看一段「祖传代码」:

python到闯关页运行这段 →
import os

base_dir = os.path.dirname(os.path.abspath(__file__))
data_path = os.path.join(base_dir, 'data', 'sub', 'foo.txt')

if os.path.exists(data_path) and os.path.isfile(data_path):
    with open(data_path, 'r', encoding='utf-8') as f:
        content = f.read()
    print(content)

是不是看着头大?

  • os.path.dirname 套 os.path.abspath 套 os.path.join
  • 判断要 os.path.exists 加 os.path.isfile
  • 方法散落在 os、os.path 两个模块

更要命的是——路径在这里只是个普通字符串。字符串不知道自己代表的是文件还是目录,所有操作都得拿着字符串去喂给一堆函数。

有没有一种写法,让路径自己「活」起来?

让 Path('data/foo.txt') 这个对象本身就知道:

  • 自己叫什么名字(p.name)
  • 自己的后缀是什么(p.suffix)
  • 自己的父目录是哪儿(p.parent)
  • 自己存不存在(p.exists())
  • 自己里面装着什么内容(p.read_text())

有的——这就是 Python 标准库 pathlib。

老 vs 新对照

操作老写法(os.path)新写法(pathlib)
拼接路径os.path.join(a, b, c)Path(a) / b / c
文件名os.path.basename(p)Path(p).name
扩展名os.path.splitext(p)[1]Path(p).suffix
父目录os.path.dirname(p)Path(p).parent
是否存在os.path.exists(p)Path(p).exists()

记住一句话:写新代码就用 pathlib,别再 os.path.join 一条道走到黑了。

本章我们专注于「路径解析、属性提取、拼接」这些纯路径操作——它们在浏览器环境里也能正常运行。read_text / write_text / mkdir 这类真访问文件系统的方法,我们留给本地环境去玩。

教学 02 / 05

一、第一个 Path

pathlib 的核心是一个类:Path。所有操作都从它开始。

1、从字符串创建 Path

python到闯关页运行这段 →
from pathlib import Path

p = Path('data/foo.txt')
print(p)
print(type(p))

输出(在 macOS / Linux 上):

data/foo.txt
<class 'pathlib.PosixPath'>

是不是奇怪——明明 Path('...'),怎么类型变成了 PosixPath?

这是因为 Path 是个聪明家伙,会根据当前操作系统自动选择子类:

  • macOS / Linux → PosixPath
  • Windows → WindowsPath

平时根本不用关心这个区别——直接 Path('...') 就完事了。

2、绝对路径

绝对路径(以 / 开头)也照样行:

python到闯关页运行这段 →
from pathlib import Path

p = Path('/tmp/data/foo.txt')
print(p)

输出:

/tmp/data/foo.txt

3、两个常用「快捷入口」

python到闯关页运行这段 →
from pathlib import Path

print(Path.home())   # 当前用户的家目录
print(Path.cwd())    # 当前工作目录

可能的输出:

/Users/walter
/Users/walter/projects/demo

对比老写法:

老 API新 API
os.path.expanduser('~')Path.home()
os.getcwd()Path.cwd()

在 Pyodide / 浏览器环境里,Path.home() 和 Path.cwd() 也能跑——只是结果是虚拟的。

教学 03 / 05

二、常用属性,一锅端

拿到一个路径,最常见的需求是「取出某一部分」——文件名、扩展名、父目录……

老写法散布在 os.path.basename、os.path.splitext、os.path.dirname 一堆函数里。Path 把这些都做成了属性。

1、.name:完整文件名(带后缀)

python到闯关页运行这段 →
from pathlib import Path

p = Path('/tmp/demo/foo.txt')
print(p.name)

输出:

foo.txt

相当于以前的 os.path.basename(...)。

2、.stem:去掉后缀的「主干」

python到闯关页运行这段 →
from pathlib import Path

p = Path('/tmp/demo/foo.txt')
print(p.stem)

输出:

foo

特别适合用来生成「同名换格式」的新文件——比如 foo.txt 转 foo.json。

3、.suffix:扩展名(带点)

python到闯关页运行这段 →
from pathlib import Path

p = Path('/tmp/demo/foo.txt')
print(p.suffix)

输出:

.txt

注意是带点的。需要不带点的,自己 [1:] 切一下就好。

4、.suffixes:所有扩展名(用于多后缀文件)

foo.tar.gz 这种「多后缀」的怎么办?

python到闯关页运行这段 →
from pathlib import Path

p = Path('archive.tar.gz')
print(p.suffix)
print(p.suffixes)

输出:

.gz
['.tar', '.gz']
  • .suffix 只给最后一个后缀
  • .suffixes 给一个列表,包含所有后缀

5、.parent:父目录

python到闯关页运行这段 →
from pathlib import Path

p = Path('/tmp/demo/sub/foo.txt')
print(p.parent)

输出:

/tmp/demo/sub

相当于 os.path.dirname(...),但读起来自然多了。

6、.parents:所有祖先目录

python到闯关页运行这段 →
from pathlib import Path

p = Path('/tmp/demo/sub/foo.txt')
print(p.parents[0])    # 一级父目录
print(p.parents[1])    # 二级祖先
print(p.parents[2])    # 三级祖先

输出:

/tmp/demo/sub
/tmp/demo
/tmp

.parents 是一个序列,可以用下标访问,也可以 for 循环遍历。

7、综合演示

python到闯关页运行这段 →
from pathlib import Path

p = Path('/Users/walter/projects/demo/main.py')

print('name   :', p.name)
print('stem   :', p.stem)
print('suffix :', p.suffix)
print('parent :', p.parent)
print('parts  :', p.parts)

输出:

name   : main.py
stem   : main
suffix : .py
parent : /Users/walter/projects/demo
parts  : ('/', 'Users', 'walter', 'projects', 'demo', 'main.py')

最后一个 .parts 把整个路径切成元组,逐段处理特别方便。

是不是发现路径在 pathlib 里彻底不是字符串了——它是一个有属性、有方法的「对象」?

练习 1 / 4提取路径的 name / suffix / stem去闯关页做这题 →
教学 04 / 05

三、用 / 拼接路径

路径拼接是最常见的操作,没有之一。

老写法

python到闯关页运行这段 →
import os

base = '/tmp'
data_path = os.path.join(base, 'demo', 'sub', 'foo.txt')
print(data_path)

输出:

/tmp/demo/sub/foo.txt

os.path.join 用是好用,但写起来嘴里念三个词「os」「path」「join」,手指都不想动了。

新写法:直接用 /

python到闯关页运行这段 →
from pathlib import Path

base = Path('/tmp')
data_path = base / 'demo' / 'sub' / 'foo.txt'
print(data_path)

输出(一模一样):

/tmp/demo/sub/foo.txt

注意——拼接路径用的是 / 这个操作符!

为什么能这么写?

因为 Path 重载了 __truediv__(除法操作符),所以 Path('/tmp') / 'demo' 这种表达式被翻译成了路径拼接。

这种设计简直是天才——路径在 URL、Linux 文件系统里用的本来就是 /,跟我们脑子里的语义完全一致。

字符串和 Path 可以混着用

python到闯关页运行这段 →
from pathlib import Path

p1 = Path('/tmp') / 'foo'         # Path / str
p2 = Path('/tmp') / Path('foo')   # Path / Path
p3 = 'foo' / Path('tmp')          # str / Path

print(p1)
print(p2)
print(p3)

输出:

/tmp/foo
/tmp/foo
foo/tmp

只要 / 的有一边是 Path,结果就是 Path 对象。

老 vs 新对照表

操作老写法新写法
拼三段os.path.join(a, b, c)Path(a) / b / c
取脚本目录os.path.join(os.path.dirname(__file__), 'data')Path(__file__).parent / 'data'
拼到家目录os.path.expanduser('~/Documents')Path.home() / 'Documents'

写一次新写法,就回不去了。

练习 2 / 4用 / 操作符拼接路径去闯关页做这题 →练习 3 / 4拿父目录和祖先目录去闯关页做这题 →
教学 05 / 05

四、文件系统操作(简介)

到目前为止,我们做的都是纯路径解析——只是在玩字符串,不真的去访问文件系统。

pathlib 的真正威力,是把「真访问文件系统」的能力也整合到了 Path 对象上。这里只做一个简介,让你知道有哪些方法——具体的真实操作请在本地环境跑。

1、判断文件 / 目录是否存在

python到闯关页运行这段 →
from pathlib import Path

p = Path('/tmp')
print(p.exists())     # 路径是否存在
print(p.is_dir())     # 是不是目录
print(p.is_file())    # 是不是文件

老写法:os.path.exists(...)、os.path.isdir(...)、os.path.isfile(...)——一对照,谁清爽不用我说了吧?

2、读写文件

python到闯关页运行这段 →
from pathlib import Path

p = Path('/tmp/demo.txt')
p.write_text('hello pathlib', encoding='utf-8')   # 一行写完
content = p.read_text(encoding='utf-8')           # 一行读完
print(content)

老写法是要 with open(...) as f: f.write(...) 加缩进的,新写法一行解决。

3、遍历目录

python到闯关页运行这段 →
from pathlib import Path

# 当前目录下一层
for child in Path('/tmp').iterdir():
    print(child)

# 当前目录的 .py 文件
for py in Path('/tmp').glob('*.py'):
    print(py)

# 递归找所有 .py 文件
for py in Path('/tmp').rglob('*.py'):
    print(py)
方法含义
.iterdir()列当前目录下一层(不递归)
.glob(pattern)当前目录通配匹配
.rglob(pattern)递归通配匹配——从子目录里一直找下去

4、创建 / 删除

python到闯关页运行这段 →
from pathlib import Path

# 创建目录(含父目录)
Path('/tmp/a/b/c').mkdir(parents=True, exist_ok=True)

# 创建空文件
Path('/tmp/note.txt').touch()

# 删除文件
Path('/tmp/note.txt').unlink(missing_ok=True)

parents=True 等于 shell 里的 mkdir -p(自动建中间目录),exist_ok=True 让目录已存在时不报错。这俩参数几乎闭着眼睛就该写上。

5、为什么本章不让你练这些?

注意——浏览器里的 Python(Pyodide)没有真实的文件系统。read_text / write_text / mkdir 这些方法虽然能跑,但只是写到一个虚拟的内存文件系统,关掉页面就没了。

所以本章的练习专注于「路径字符串解析」——这部分在哪儿都能跑,一通百通。

等你回到本地终端,pathlib 的全套真本事才会真正发光。

练习 4 / 4多后缀文件:.suffixes去闯关页做这题 →

读完了?动手练一遍才算真会。

去闯关模式练习 →