3. pathlib 模块入门:Path 对象的基本操作,告别字符串拼接
说实话,我刚开始写 Python 那会儿,处理文件路径全靠字符串拼接。什么 os.path.join、os.path.abspath,用起来总觉得别扭。直到有一天,我在一个跨平台项目里被路径分隔符坑惨了——Windows 上用反斜杠,Linux 上用正斜杠,代码跑起来直接报错。嗯,从那以后,我就彻底倒向了 pathlib。
今天咱们就来聊聊这个模块。它不是什么新东西,Python 3.4 就有了,但很多人到现在还在用老一套。我个人觉得,pathlib 最大的好处就是:把路径当对象来操作,而不是当字符串来拼接。
3.1 为什么是 pathlib?
你想想看,字符串拼接路径有多脆弱?
# 老方式
import os
path = os.path.join("folder", "subfolder", "file.txt")
print(path) # 输出:folder/subfolder/file.txt(Linux)
# 输出:folder\subfolder\file.txt(Windows)
这还算好的。一旦你遇到带空格的目录名,或者需要动态拼接多层路径,代码立马变得又臭又长。我在项目中遇到过最离谱的情况:一个同事用字符串拼接拼出了 "//folder//file.txt",双斜杠在 Linux 上居然也能跑,但到了 Windows 上直接崩了。
pathlib 的核心思想很简单:用 Path 对象表示路径,所有操作都通过对象方法完成。跨平台?它自动处理。路径拼接?用 / 运算符就行。
核心优势:
- 面向对象,代码更直观
- 自动处理跨平台分隔符
- 支持运算符重载(
/拼接路径) - 方法链式调用,简洁优雅
3.2 创建 Path 对象
创建 Path 对象的方式有好几种,我一般用这三种:
from pathlib import Path
# 方式一:直接传字符串
p1 = Path("/home/user/projects")
p2 = Path("relative/path/to/file.txt")
# 方式二:从当前目录创建
p3 = Path.cwd() # 当前工作目录
p4 = Path.home() # 用户主目录
# 方式三:拼接多个部分
p5 = Path("folder", "subfolder", "file.txt")
print(p5) # 输出:folder/subfolder/file.txt
这里有个小细节:Path 对象不检查路径是否存在。它只是一个路径的抽象表示。你创建了一个不存在的路径,它也不会报错。我曾经因为这个踩过坑——以为路径存在,结果读写文件时才发现目录没创建。嗯,后面我们会讲怎么检查。
3.3 路径的属性和方法
Path 对象提供了丰富的属性和方法,让你能轻松获取路径的各个部分。我整理了一张表,方便你查阅:
| 属性/方法 | 说明 | 示例(Path("/home/user/file.txt")) |
|---|---|---|
.name |
文件名(含扩展名) | "file.txt" |
.stem |
文件名(不含扩展名) | "file" |
.suffix |
文件扩展名 | ".txt" |
.parent |
父目录路径 | Path("/home/user") |
.parents |
所有父目录(可索引) | Path("/home/user").parents[0] → Path("/home") |
.anchor |
路径的根(如 / 或 C:\) |
"/" |
.parts |
路径各部分组成的元组 | ("/", "home", "user", "file.txt") |
举个例子,你就知道有多方便了:
from pathlib import Path
path = Path("/home/user/projects/data/report.csv")
print(f"文件名:{path.name}") # report.csv
print(f"主文件名:{path.stem}") # report
print(f"扩展名:{path.suffix}") # .csv
print(f"父目录:{path.parent}") # /home/user/projects/data
print(f"祖父目录:{path.parents[1]}") # /home/user/projects
print(f"根目录:{path.anchor}") # /
print(f"各部分:{path.parts}") # ('/', 'home', 'user', 'projects', 'data', 'report.csv')
你看,以前用 os.path 要写一堆 os.path.dirname、os.path.basename,现在一个属性就搞定了。我刚开始用的时候,觉得这简直太爽了。
3.4 路径拼接与解析
路径拼接是 pathlib 最亮眼的功能之一。它重载了 / 运算符,让路径拼接变得像数学运算一样自然:
from pathlib import Path
base = Path("/home/user/projects")
config = base / "config" / "settings.json"
print(config) # 输出:/home/user/projects/config/settings.json
# 也可以和字符串混合使用
data_dir = base / "data"
csv_file = data_dir / "2024" / "report.csv"
print(csv_file) # 输出:/home/user/projects/data/2024/report.csv
注意,/ 运算符左边必须是 Path 对象,右边可以是字符串或其他 Path 对象。我见过有人写 "folder" / Path("file.txt"),这不行,会报错。记住:左边是 Path,右边随意。
除了拼接,pathlib 还提供了路径解析功能:
from pathlib import Path
# 解析相对路径
rel_path = Path("data/../config/settings.json")
resolved = rel_path.resolve()
print(resolved) # 输出:/当前工作目录/config/settings.json
# 判断是绝对路径还是相对路径
print(Path("/home/user").is_absolute()) # True
print(Path("relative/path").is_absolute()) # False
小技巧:如果你需要把 Path 对象转成字符串(比如传给某些旧版 API),用 str(path) 就行。它会自动使用当前操作系统的路径分隔符。
3.5 路径检查与操作
光有路径表示还不够,我们经常需要检查路径是否存在、是文件还是目录。这些 pathlib 都帮你封装好了:
from pathlib import Path
path = Path("test_file.txt")
# 检查路径是否存在
print(path.exists()) # True 或 False
# 检查是文件还是目录
print(path.is_file()) # 是否是文件
print(path.is_dir()) # 是否是目录
# 检查路径的其他属性
print(path.is_symlink()) # 是否是符号链接
print(path.is_absolute()) # 是否是绝对路径
我曾经在写一个文件清理脚本时,忘了检查路径是否存在就直接删除,结果程序抛异常了。后来我养成了习惯:操作前先检查,用 exists() 保底。
创建目录和文件也很简单:
from pathlib import Path
# 创建目录(如果不存在)
Path("output/data").mkdir(parents=True, exist_ok=True)
# 创建文件(如果不存在)
file_path = Path("output/data/log.txt")
file_path.touch(exist_ok=True)
# 删除文件
file_path.unlink(missing_ok=True) # Python 3.8+
# 删除空目录
Path("empty_dir").rmdir()
注意 mkdir 的两个参数:parents=True 表示如果父目录不存在,自动创建;exist_ok=True 表示如果目录已存在,不报错。这两个参数我几乎每次都加,省心。
3.6 遍历目录
遍历目录也是家常便饭。以前用 os.listdir 或 os.walk,现在用 pathlib 的方法更直观:
from pathlib import Path
data_dir = Path("data")
# 列出当前目录下的所有内容
for item in data_dir.iterdir():
print(item.name)
# 递归遍历所有文件(用 glob 模式匹配)
for py_file in data_dir.rglob("*.py"):
print(py_file)
# 只遍历当前目录下的特定文件
for txt_file in data_dir.glob("*.txt"):
print(txt_file)
rglob 是我最常用的方法。它递归搜索所有子目录,配合通配符,能快速找到你想要的文件。我在做日志分析时,经常用 rglob("*.log") 一下子找出所有日志文件。
避坑指南:我曾经用 iterdir() 遍历一个大目录,里面有几万个文件,结果程序卡了好久。后来才发现,iterdir() 是惰性求值的,但如果你把它转成列表(比如 list(path.iterdir())),就会一次性加载所有内容。大目录下慎用!
3.7 实战:批量重命名文件
光说不练假把式。咱们来个实际例子:批量重命名目录下的所有 .txt 文件,加上日期前缀。
from pathlib import Path
from datetime import datetime
def rename_files_with_date(directory):
"""给目录下所有 .txt 文件加上日期前缀"""
dir_path = Path(directory)
if not dir_path.exists() or not dir_path.is_dir():
print(f"目录不存在:{directory}")
return
today = datetime.now().strftime("%Y%m%d")
for file_path in dir_path.glob("*.txt"):
# 新文件名:日期_原文件名
new_name = f"{today}_{file_path.name}"
new_path = file_path.parent / new_name
# 避免覆盖已有文件
if new_path.exists():
print(f"跳过:{new_name} 已存在")
continue
file_path.rename(new_path)
print(f"重命名:{file_path.name} → {new_name}")
# 使用示例
rename_files_with_date("logs")
你看,整个代码没有一处字符串拼接路径。所有路径操作都通过 Path 对象完成,清晰又安全。我每次写这种脚本,都会感叹:pathlib 真是跨平台开发的救星。
3.8 小结
好了,这一章的内容就这些。说白了,pathlib 就是让你用面向对象的方式操作路径,告别那些烦人的字符串拼接。我个人建议:从现在开始,新项目一律用 pathlib。老项目如果还在用 os.path,也可以逐步迁移过来。
下一章我们会深入 pathlib 的高级用法,包括路径比较、权限检查、以及和 os 模块的互操作。到时候你会发现,原来路径处理可以这么优雅。