4 道练习题·预计 30 分钟·做对一题解锁下一段
先看一段「祖传代码」:
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.joinos.path.exists 加 os.path.isfileos、os.path 两个模块更要命的是——路径在这里只是个普通字符串。字符串不知道自己代表的是文件还是目录,所有操作都得拿着字符串去喂给一堆函数。
让 Path('data/foo.txt') 这个对象本身就知道:
p.name)p.suffix)p.parent)p.exists())p.read_text())有的——这就是 Python 标准库 pathlib。
| 操作 | 老写法(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这类真访问文件系统的方法,我们留给本地环境去玩。
pathlib 的核心是一个类:Path。所有操作都从它开始。
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 是个聪明家伙,会根据当前操作系统自动选择子类:
PosixPathWindowsPath平时根本不用关心这个区别——直接 Path('...') 就完事了。
绝对路径(以 / 开头)也照样行:
from pathlib import Path
p = Path('/tmp/data/foo.txt')
print(p)输出:
/tmp/data/foo.txt
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()也能跑——只是结果是虚拟的。
拿到一个路径,最常见的需求是「取出某一部分」——文件名、扩展名、父目录……
老写法散布在 os.path.basename、os.path.splitext、os.path.dirname 一堆函数里。Path 把这些都做成了属性。
.name:完整文件名(带后缀)from pathlib import Path
p = Path('/tmp/demo/foo.txt')
print(p.name)输出:
foo.txt
相当于以前的 os.path.basename(...)。
.stem:去掉后缀的「主干」from pathlib import Path
p = Path('/tmp/demo/foo.txt')
print(p.stem)输出:
foo
特别适合用来生成「同名换格式」的新文件——比如 foo.txt 转 foo.json。
.suffix:扩展名(带点)from pathlib import Path
p = Path('/tmp/demo/foo.txt')
print(p.suffix)输出:
.txt
注意是带点的。需要不带点的,自己 [1:] 切一下就好。
.suffixes:所有扩展名(用于多后缀文件)foo.tar.gz 这种「多后缀」的怎么办?
from pathlib import Path
p = Path('archive.tar.gz')
print(p.suffix)
print(p.suffixes)输出:
.gz
['.tar', '.gz']
.suffix 只给最后一个后缀.suffixes 给一个列表,包含所有后缀.parent:父目录from pathlib import Path
p = Path('/tmp/demo/sub/foo.txt')
print(p.parent)输出:
/tmp/demo/sub
相当于 os.path.dirname(...),但读起来自然多了。
.parents:所有祖先目录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 循环遍历。
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 里彻底不是字符串了——它是一个有属性、有方法的「对象」?
请:
pathlib 导入 Pathp = Path('/tmp/data/foo.txt').name、.suffix、.stem输出应该是:
foo.txt
.txt
foo
注意:print(p.name) 输出的是 foo.txt,不是 'foo.txt'——直接打印不带引号。