pathlib 是 Python 3.4+ 引入的面向对象的文件路径处理模块,比 os.path 更现代、更直观。它提供了 Path 类来表示文件系统路径。
1. 基本导入和初始化
python
from pathlib import Path
# 创建路径对象
p1 = Path() # 当前目录
print(p1) # .
p2 = Path('documents') # 相对路径
print(p2) # documents
p3 = Path('/home/user/file.txt') # 绝对路径
print(p3) # /home/user/file.txt
# 支持多级路径拼接(不需要 join)
p4 = Path('home') / 'user' / 'docs' / 'readme.md'
print(p4) # home/user/docs/readme.md
2. 路径拼接与拆分
2.1 使用 / 运算符拼接
python
# 使用斜杠拼接(跨平台)
root = Path('/home/user')
full_path = root / 'documents' / 'notes.txt'
print(full_path) # /home/user/documents/notes.txt
# 也可以使用 joinpath()
path = Path('/tmp')
full = path.joinpath('data', 'logs', 'app.log')
print(full) # /tmp/data/logs/app.log
# 支持字符串和Path对象混用
base = Path('/var')
new = base / 'www' / 'html'
print(new) # /var/www/html
2.2 获取路径各部分
python
path = Path('/home/user/documents/file.tar.gz')
# 获取文件名(包含所有扩展名)
print(path.name) # file.tar.gz
# 获取不带扩展名的文件名
print(path.stem) # file.tar
# 获取扩展名(最后一个点之后)
print(path.suffix) # .gz
# 获取所有扩展名(Python 3.12+)
print(path.suffixes) # ['.tar', '.gz']
# 获取父目录
print(path.parent) # /home/user/documents
print(path.parent.parent) # /home/user
# 获取所有祖先
for parent in path.parents:
print(parent)
# /home/user/documents
# /home/user
# /home
# /
# 获取根目录
print(path.anchor) # /
2.3 拆分组装示例
python
# 实际应用:替换文件扩展名
file = Path('report.xlsx')
new_file = file.with_suffix('.pdf')
print(new_file) # report.pdf
# 修改文件名
old = Path('data/old_name.txt')
new = old.with_name('new_name.txt')
print(new) # data/new_name.txt
# 组合使用
config = Path('/etc/nginx/nginx.conf')
backup = config.with_name(config.stem + '.backup' + config.suffix)
print(backup) # /etc/nginx/nginx.backup.conf
3. 路径检查与属性
python
# 假设当前目录有 test.txt 文件和 mydir 目录
path_file = Path('test.txt')
path_dir = Path('mydir')
path_link = Path('symlink') # 假设存在符号链接
# 存在性检查
print(path_file.exists()) # True
print(Path('nonexist').exists()) # False
# 类型检查
print(path_file.is_file()) # True
print(path_dir.is_file()) # False
print(path_file.is_dir()) # False
print(path_dir.is_dir()) # True
# 其他检查
print(path_file.is_absolute()) # False
print(Path('/home/file.txt').is_absolute()) # True
print(path_file.is_symlink()) # False
print(path_link.is_symlink()) # True
# 路径比较(自动跨平台)
p1 = Path('/home/user')
p2 = Path('/home') / 'user'
print(p1 == p2) # True
4. 目录操作
4.1 创建和删除目录
python
# 创建单级目录
new_dir = Path('new_folder')
new_dir.mkdir(exist_ok=True) # exist_ok=True 避免已存在时报错
print(new_dir.exists()) # True
# 创建多级目录
nested = Path('parent/child/grandchild')
nested.mkdir(parents=True, exist_ok=True)
print(nested.exists()) # True
# 删除目录(必须为空)
empty_dir = Path('temp_empty')
empty_dir.mkdir()
empty_dir.rmdir() # 删除空目录
# 删除目录及内容(Python 3.12+)
# import shutil
# shutil.rmtree('parent') # 递归删除
4.2 列出目录内容
python
# 列出所有内容
docs = Path('/home/user/documents')
for item in docs.iterdir():
print(item.name)
# 输出示例:
# report.pdf
# images
# notes.txt
# backup
# 使用 glob 模式匹配
# 查找所有 .txt 文件
for txt_file in docs.glob('*.txt'):
print(txt_file)
# /home/user/documents/notes.txt
# /home/user/documents/readme.txt
# 递归查找(** 表示任意子目录)
for py_file in docs.glob('**/*.py'):
print(py_file)
# /home/user/documents/scripts/main.py
# /home/user/documents/tools/helper.py
# 区分文件和目录
for item in docs.iterdir():
if item.is_file():
print(f"文件: {item.name}")
elif item.is_dir():
print(f"目录: {item.name}")
5. 文件操作
5.1 读写文件
python
# 写文件
file_path = Path('example.txt')
# 方法1:write_text
file_path.write_text('Hello, World!', encoding='utf-8')
print(file_path.read_text(encoding='utf-8')) # Hello, World!
# 方法2:write_bytes(二进制)
bin_path = Path('data.bin')
bin_path.write_bytes(b'\x00\x01\x02\x03')
print(bin_path.read_bytes()) # b'\x00\x01\x02\x03'
# 方法3:使用 open() 方法(上下文管理)
with file_path.open('a', encoding='utf-8') as f:
f.write('\nAppended line')
print(file_path.read_text(encoding='utf-8'))
# Hello, World!
# Appended line
# 删除文件
file_path.unlink(missing_ok=True) # missing_ok=True 不报错
5.2 复制和移动文件
python
from pathlib import Path
import shutil
# 复制文件
src = Path('source.txt')
dst = Path('backup/source_copy.txt')
# 创建目标目录(如果需要)
dst.parent.mkdir(parents=True, exist_ok=True)
# 复制
shutil.copy2(src, dst) # 保留元数据
print(dst.exists()) # True
# 移动/重命名文件
old = Path('old_name.txt')
new = Path('new_name.txt')
old.rename(new) # 重命名
print(new.exists()) # True
print(old.exists()) # False
# 移动文件到其他目录
src = Path('data.txt')
dst = Path('archive/data.txt')
src.rename(dst)
print(dst.exists()) # True
6. 文件属性和信息
python
path = Path('test.txt')
# 文件大小(字节)
print(path.stat().st_size) # 1024
# 获取文件信息
stat = path.stat()
print(f"大小: {stat.st_size} 字节") # 大小: 1024 字节
print(f"权限: {oct(stat.st_mode)}") # 权限: 0o100644
print(f"最后访问: {stat.st_atime}") # 最后访问: 1747556123.45
print(f"最后修改: {stat.st_mtime}") # 最后修改: 1747556100.00
# 使用 datetime 格式化时间
from datetime import datetime
mtime = datetime.fromtimestamp(stat.st_mtime)
print(f"修改时间: {mtime}") # 修改时间: 2026-05-18 10:15:00
# 获取绝对路径
print(path.absolute()) # /home/user/project/test.txt
print(path.resolve()) # 解析符号链接后的绝对路径
# 获取相对路径
current = Path.cwd()
abs_path = Path('/home/user/project/test.txt')
rel_path = abs_path.relative_to(current)
print(rel_path) # test.txt
7. 实战示例
7.1 批量重命名文件
python
def batch_rename(directory, old_ext, new_ext):
"""批量修改文件扩展名"""
dir_path = Path(directory)
for file_path in dir_path.glob(f'*.{old_ext}'):
new_path = file_path.with_suffix(f'.{new_ext}')
file_path.rename(new_path)
print(f"重命名: {file_path.name} -> {new_path.name}")
# 重命名: image.jpg -> image.png
# 重命名: doc.jpg -> doc.png
# 使用示例
# batch_rename('./images', 'jpg', 'png')
7.2 递归统计文件类型
python
from collections import Counter
def count_file_types(directory):
"""递归统计目录中各类型文件数量"""
dir_path = Path(directory)
type_counter = Counter()
for file_path in dir_path.rglob('*'):
if file_path.is_file():
suffix = file_path.suffix or '无扩展名'
type_counter[suffix] += 1
return type_counter
# 使用示例
stats = count_file_types('/home/user/project')
for ext, count in stats.most_common(5):
print(f"{ext:10} : {count} 个")
# .py : 42 个
# .txt : 15 个
# .md : 8 个
# .json : 5 个
# 无扩展名 : 3 个
7.3 整理文件到日期目录
python
from datetime import datetime
import shutil
def organize_by_date(directory):
"""按文件修改日期整理文件到子目录"""
base_path = Path(directory)
for file_path in base_path.iterdir():
if not file_path.is_file():
continue
# 获取修改日期
mtime = datetime.fromtimestamp(file_path.stat().st_mtime)
date_dir = base_path / mtime.strftime('%Y/%m')
# 创建日期目录
date_dir.mkdir(parents=True, exist_ok=True)
# 移动文件
dest = date_dir / file_path.name
shutil.move(str(file_path), str(dest))
print(f"移动: {file_path.name} -> {dest}")
# 移动: report.pdf -> ./2026/05/report.pdf
# 使用示例
# organize_by_date('./downloads')
7.4 查找和清理空文件夹
python
def find_empty_dirs(directory):
"""递归查找所有空文件夹"""
base_path = Path(directory)
empty_dirs = []
# rglob 查找所有目录,sort 确保先处理深层目录
for dir_path in sorted(base_path.rglob('*'), reverse=True):
if dir_path.is_dir():
try:
next(dir_path.iterdir()) # 尝试获取第一个子项
except StopIteration:
empty_dirs.append(dir_path)
return empty_dirs
def clean_empty_dirs(directory, dry_run=True):
"""清理空文件夹"""
empty_dirs = find_empty_dirs(directory)
for dir_path in empty_dirs:
if dry_run:
print(f"[模拟] 将删除: {dir_path}")
# [模拟] 将删除: /tmp/parent/empty
else:
dir_path.rmdir()
print(f"已删除: {dir_path}")
# 已删除: /tmp/parent/empty
# 使用示例
# clean_empty_dirs('./data', dry_run=True) # 先预览
# clean_empty_dirs('./data', dry_run=False) # 实际删除
7.5 目录树展示
python
def display_tree(directory, prefix='', level=0, max_level=3):
"""以树形结构显示目录内容"""
if level > max_level:
return
path = Path(directory)
items = sorted(path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
for i, item in enumerate(items):
is_last = (i == len(items) - 1)
connector = '└── ' if is_last else '├── '
print(f"{prefix}{connector}{item.name}{'/' if item.is_dir() else ''}")
if item.is_dir():
extension = ' ' if is_last else '│ '
display_tree(item, prefix + extension, level + 1, max_level)
# 使用示例
print("项目目录结构:")
display_tree('/home/user/project', max_level=2)
# 项目目录结构:
# ├── docs/
# │ ├── readme.md
# │ └── api/
# │ └── index.html
# ├── src/
# │ ├── main.py
# │ └── utils.py
# └── tests/
# └── test_main.py
8. Path 对象属性速查表
| 属性/方法 | 说明 | 示例 |
|---|---|---|
.name |
文件名(含扩展名) | 'file.tar.gz' |
.stem |
文件名(不含扩展名) | 'file.tar' |
.suffix |
最后一个扩展名 | '.gz' |
.suffixes |
所有扩展名列表 | ['.tar', '.gz'] |
.parent |
父目录 | '/home/user' |
.parents |
所有祖先(可索引) | [Path('/home'), Path('/')] |
.anchor |
根目录部分 | '/' 或 'C:\\' |
.absolute() |
绝对路径 | '/home/user/file.txt' |
.resolve() |
解析后的绝对路径 | '/home/user/file.txt' |
.exists() |
是否存在 | True |
.is_file() |
是否为文件 | True |
.is_dir() |
是否为目录 | False |
.is_absolute() |
是否绝对路径 | True |
.is_symlink() |
是否符号链接 | False |
.stat() |
文件状态信息 | os.stat_result |
.glob(pattern) |
匹配模式(非递归) | 生成器 |
.rglob(pattern) |
递归匹配 | 生成器 |
.iterdir() |
遍历目录 | 生成器 |
.mkdir() |
创建目录 | - |
.rmdir() |
删除空目录 | - |
.unlink() |
删除文件 | - |
.rename(target) |
重命名/移动 | - |
.read_text() |
读取文本 | 'content' |
.write_text() |
写入文本 | - |
.read_bytes() |
读取二进制 | b'\x00\x01' |
.write_bytes() |
写入二进制 | - |
.with_name(name) |
替换文件名 | '/home/new.txt' |
.with_suffix(suffix) |
替换扩展名 | '/home/file.pdf' |
9. pathlib vs os.path 对比
python
# os.path 风格
import os.path
folder = 'data'
filename = 'file.txt'
path = os.path.join(folder, filename)
dirname = os.path.dirname(path)
basename = os.path.basename(path)
exists = os.path.exists(path)
size = os.path.getsize(path)
# pathlib 风格(更清晰)
from pathlib import Path
path = Path('data') / 'file.txt'
dirname = path.parent
basename = path.name
exists = path.exists()
size = path.stat().st_size
10. 最佳实践
python
# ✅ 推荐:使用 Path 对象
from pathlib import Path
# 获取当前文件所在目录
current_dir = Path(__file__).parent
# 项目根目录(假设在项目根目录执行)
project_root = Path.cwd()
# 构建数据路径
data_path = project_root / 'data' / 'raw_data.csv'
# 检查并创建目录
data_path.parent.mkdir(parents=True, exist_ok=True)
# 处理文件
if data_path.exists() and data_path.is_file():
content = data_path.read_text(encoding='utf-8')
# 处理内容...
# 遍历文件
for log_file in project_root.glob('logs/**/*.log'):
if log_file.stat().st_size > 10 * 1024 * 1024: # 大于10MB
archive = log_file.with_suffix('.log.gz')
print(f"归档大文件: {log_file} -> {archive}")
pathlib 提供了面向对象的、直观的路径操作方式,强烈推荐在新项目中使用它代替 os.path!