第24课 - 现代文件系统操作(pathlib 模块)
学习目标
完成本课学习后,你将能够:
- 理解 pathlib 模块的核心优势,并能在新项目中优先选择它而非 os.path
- 熟练创建、拼接和操作路径对象,完成常见的文件系统任务
- 使用 pathlib 进行文件搜索、遍历和属性检查,高效管理文件资源
- 掌握 pathlib 与传统 os 模块的协作方法,实现平滑过渡
核心概念
想象一下,你每天都要整理一个巨大的文件柜。传统的 os.path 模块就像是一套工具箱,你每次操作文件(比如"找到A文件夹里的B文件")都需要从箱子里拿出不同的工具(函数)来组合使用。而 pathlib 模块则像是一个智能的文件柜管理机器人,它把路径本身当作一个对象,这个对象自带了"寻找"、"复制"、"查看信息"等所有功能。你只需要告诉这个"路径对象"你想做什么,它就知道该如何完成。
pathlib 的三大优势
- 面向对象:路径不是一个字符串,而是一个
Path对象,拥有自己的方法和属性。 - 可读性强:代码更直观,更接近自然语言。
- 跨平台:自动处理 Windows(
\)和 macOS/Linux(/)路径分隔符的差异。
快速对比:os.path vs pathlib
import os
from pathlib import Path
# 传统方式:拼接路径
old_path = os.path.join('documents', 'project', 'data.csv')
# pathlib方式:
new_path = Path('documents') / 'project' / 'data.csv'
print(old_path) # documents/project/data.csv (在Unix上)
print(new_path) # documents/project/data.csv
# 检查文件是否存在
os.path.exists(old_path)
new_path.exists()
# 获取文件名
os.path.basename(old_path)
new_path.name
看到区别了吗?pathlib 使用 / 运算符来拼接路径,非常直观,而且所有操作都是对 Path 对象的方法调用。
代码示例
让我们通过一个完整的例子来学习 pathlib 的核心功能。这个例子会创建一个模拟的项目目录结构,并演示如何操作它。
from pathlib import Path
import shutil
# 1. 创建一个Path对象
# Path() 可以接受一个或多个字符串参数,也可以接受一个现有的Path对象
base_dir = Path('my_project') # 相对路径
# 如果你想创建一个绝对路径,可以使用 resolve()
# absolute_path = base_dir.resolve()
print(f"创建的路径对象: {base_dir}")
print(f"这是绝对路径吗? {base_dir.is_absolute()}")
# 2. 创建目录和文件
# mkdir() 创建目录。parents=True 会创建所有必要的父目录(类似 mkdir -p)
base_dir.mkdir(parents=True, exist_ok=True)
# 使用 / 运算符拼接子路径,然后创建文件并写入内容
src_dir = base_dir / 'src'
src_dir.mkdir(exist_ok=True)
main_py = src_dir / 'main.py'
main_py.write_text("print('Hello, World!')\n", encoding='utf-8')
config_file = base_dir / 'config.json'
config_file.write_text('{"name": "demo"}\n', encoding='utf-8')
# 3. 遍历目录
print("\n遍历 'my_project' 目录:")
for item in base_dir.iterdir():
# item 本身就是一个 Path 对象
if item.is_file():
print(f" 文件: {item.name} (大小: {item.stat().st_size} bytes)")
elif item.is_dir():
print(f" 目录: {item.name}/")
# 4. 搜索文件 (使用 glob 或 rglob)
print("\n查找所有 .json 文件:")
json_files = list(base_dir.glob('*.json')) # 只在当前目录查找
for file in json_files:
print(f" {file}")
print("\n递归查找所有 .py 文件 (包含子目录):")
py_files = list(base_dir.rglob('*.py')) # rglob 递归查找
for file in py_files:
print(f" {file}")
# 5. 读取和修改文件
print(f"\n读取 {main_py} 的内容:")
content = main_py.read_text(encoding='utf-8')
print(content)
# 重命名文件
new_main_py = src_dir / 'app.py'
main_py.rename(new_main_py)
print(f"文件已重命名为: {new_main_py}")
# 6. 其他有用的属性和方法
print(f"\n文件 {new_main_py} 的属性:")
print(f" 文件名: {new_main_py.name}") # app.py
print(f" 文件后缀: {new_main_py.suffix}") # .py
print(f" 不带后缀的文件名: {new_main_py.stem}") # app
print(f" 父目录: {new_main_py.parent}") # my_project/src
# 7. 清理:删除我们创建的所有内容
# 注意:在真实项目中要非常小心地使用删除操作!
print("\n正在清理创建的模拟目录...")
# 使用 shutil.rmtree 来删除整个目录树
shutil.rmtree(base_dir)
print("清理完成。")
运行结果示例 (输出顺序可能略有不同):
创建的路径对象: my_project
这是绝对路径吗? False
遍历 'my_project' 目录:
文件: config.json (大小: 19 bytes)
目录: src/
查找所有 .json 文件:
my_project/config.json
递归查找所有 .py 文件 (包含子目录):
my_project/src/main.py
读取 my_project/src/main.py 的内容:
print('Hello, World!')
文件已重命名为: my_project/src/app.py
文件 my_project/src/app.py 的属性:
文件名: app.py
文件后缀: .py
不带后缀的文件名: app
父目录: my_project/src
正在清理创建的模拟目录...
清理完成。
实践练习
练习 1:路径创建与文件写入(初级)
要求:使用 pathlib,在当前工作目录下创建一个名为 practice_24 的目录,在该目录中创建一个 notes.txt 文件,并写入今天的日期。
预期输出:目录和文件被成功创建,文件内容为类似 2023-10-27 的日期字符串。
练习 2:文件整理模拟器(中级)
要求:给定一个包含多种类型文件(如 .jpg, .txt, .docx, .pdf)的文件夹路径(你可以手动创建几个测试文件)。编写一个函数,使用 pathlib 遍历该文件夹,并将文件移动到以它们扩展名命名的子文件夹中(例如,所有 .jpg 文件移动到 images 子目录,.txt 移动到 texts 子目录)。
预期输出:
移动 picture1.jpg -> ./images/picture1.jpg
移动 report.txt -> ./texts/report.txt
移动 essay.docx -> ./documents/essay.docx
练习 3:项目文件扫描仪(高级)
要求:编写一个程序,扫描一个指定的项目目录(例如,包含代码和文档的目录),生成一份简单的报告。报告应列出:
- 总文件数和总目录数。
- 按文件扩展名分类的文件数量统计(例如:
.py: 15,.txt: 3)。 - 找到的最大和最小的文件(按文件大小)的路径和大小。 预期输出(格式自拟):
=== 项目扫描报告 ===
根目录: /path/to/your/project
总文件数: 25
总目录数: 8
文件类型统计:
.py: 15
.md: 5
.txt: 5
最大文件: /path/to/your/project/data/big_file.bin (1024 KB)
最小文件: /path/to/your/project/config.ini (12 bytes)
常见错误
-
混淆相对路径与绝对路径:
p = Path('file.txt') # 这是相对路径,相对于当前工作目录 p.absolute() # 这会将其转换为绝对路径,但不改变原对象 p = p.resolve() # resolve() 也能处理符号链接,更推荐用于获取绝对路径建议:在需要明确位置时,使用
resolve()确保路径的确定性。 -
忘记检查文件/目录是否存在:
p = Path('nonexistent_file.txt') # p.read_text() # 这会直接引发 FileNotFoundError if p.exists(): content = p.read_text() else: print("文件不存在")建议:在进行读取、删除等操作前,养成使用
exists()、is_file()、is_dir()检查的习惯。 -
路径拼接时使用字符串加法:
p = Path('folder') wrong_path = p + '/file.txt' # 错误!会导致 TypeError correct_path = p / 'file.txt' # 正确!使用 / 运算符建议:始终使用
/运算符或Path的joinpath()方法。 -
编码问题:
p = Path('file_with_chinese.txt') # p.read_text() # 如果文件编码不是UTF-8,可能会失败 p.read_text(encoding='gbk') # 指定正确的编码建议:读写文本文件时,明确指定
encoding参数。 -
递归操作时的风险:
# 删除当前目录下所有名为 'temp' 的目录(非常危险!) for temp_dir in Path('.').rglob('temp'): if temp_dir.is_dir(): shutil.rmtree(temp_dir) # 极其危险,可能误删重要数据建议:递归操作前先打印出所有将要受影响的路径,确认无误。考虑使用
dry-run模式。
小结
- 核心思想:
pathlib将文件系统路径视为 对象,而非字符串。这带来了更优雅、更 Pythonic 的代码风格。 - 关键操作:
- 创建与拼接:
Path('base') / 'sub' / 'file.ext' - 目录操作:
mkdir(),iterdir(),rmdir()(删除空目录) - 文件操作:
read_text(),write_text(),read_bytes(),write_bytes() - 检查与查询:
exists(),is_file(),is_dir(),name,suffix,stat() - 搜索:
glob('pattern')(非递归),rglob('pattern')(递归)
- 创建与拼接:
- 最佳实践:对于新项目,优先使用
pathlib。它与shutil(用于复制、移动整个目录树)等标准库配合良好,是现代 Python 文件操作的首选。 - 下一步预告:在掌握了如何用
pathlib这个"智能工具"操作文件系统后,我们将进入 面向对象编程 的世界。你会更深刻地理解Path本身就是类的一个实例,而你将学习如何创建自己的类来构建更复杂的程序。