Python Pathlib 库指南
Pathlib 是 Python 3.4+ 中引入的面向对象的文件系统路径处理库,提供了比传统 os.path 更直观、更 Pythonic 的方式来处理文件路径和文件系统操作。
目录
- 1. Pathlib 核心概念
- 2. 路径操作详解
- 3. 文件系统操作
- 4. 目录遍历与模式匹配
- 5. 高级路径处理
- 6. 跨平台开发
- 7. 性能优化
- 8. 与传统模块对比
- 9. 最佳实践
- 10. 常见问题解决
1. Pathlib 核心概念
1.1 Path 对象类型
Pathlib 提供了两种主要路径类型:
具体路径类:实际访问文件系统
- WindowsPath:Windows 系统专用
- PosixPath:Unix/Linux/MacOS 系统专用
纯路径类:不访问文件系统
- PureWindowsPath
- PurePosixPath
from pathlib import Path, PurePath# 自动选择适合当前系统的路径类型
p = Path('file.txt') # WindowsPath 或 PosixPath# 创建纯路径对象
pure_path = PurePath('/usr/bin/python')
1.2 Path 对象特性
- 不可变对象:所有操作返回新路径对象
- 链式操作:支持方法链式调用
- 路径归一化:自动处理路径分隔符和冗余部分
2. 路径操作详解
2.1 路径创建与拼接
# 创建绝对路径
abs_path = Path('/home/user/docs')# 创建相对路径
rel_path = Path('src/utils')# 使用家目录
home_path = Path.home() # /home/user 或 C:\Users\User# 拼接路径
config_path = home_path / '.config' / 'app' / 'settings.ini'# 使用 joinpath
log_path = Path('logs').joinpath('2023', 'app.log')
2.2 路径组件访问
p = Path('/home/user/docs/report.tar.gz')p.name # 'report.tar.gz'
p.stem # 'report.tar' (Python 3.9+)
p.suffix # '.gz'
p.suffixes # ['.tar', '.gz']
p.parent # Path('/home/user/docs')
p.parents # 生成器: [Path('/home/user/docs'), Path('/home/user'), ...]
p.anchor # '/' 或 'C:\\'
p.drive # '' 或 'C:' (Windows)
p.root # '/' 或 '\\' (Windows)
2.3 路径修改方法
p = Path('/home/user/docs/report.txt')# 修改文件名
p.with_name('data.csv') # /home/user/docs/data.csv# 修改主干名 (Python 3.9+)
p.with_stem('summary') # /home/user/docs/summary.txt# 修改后缀
p.with_suffix('.pdf') # /home/user/docs/report.pdf# 添加后缀
p.with_suffix(p.suffix + '.bak') # /home/user/docs/report.txt.bak
2.4 路径属性检查
p = Path('script.py')p.exists() # 路径是否存在
p.is_file() # 是否是文件
p.is_dir() # 是否是目录
p.is_symlink() # 是否是符号链接
p.is_block_device() # 是否是块设备
p.is_char_device() # 是否是字符设备
p.is_fifo() # 是否是命名管道
p.is_socket() # 是否是套接字
p.is_mount() # 是否是挂载点 (Python 3.7+)
p.is_absolute() # 是否是绝对路径
p.is_relative_to('/home') # 是否相对于指定路径 (Python 3.9+)
3. 文件系统操作
3.1 文件读写
# 文本文件操作
content = Path('data.txt').read_text(encoding='utf-8')
Path('output.txt').write_text('Hello\nWorld', encoding='utf-8')# 二进制文件操作
data = Path('image.png').read_bytes()
Path('copy.png').write_bytes(data)# 追加内容
with Path('log.txt').open('a', encoding='utf-8') as f:f.write('\nNew log entry')
3.2 目录操作
# 创建目录
Path('new_dir').mkdir(exist_ok=True)
Path('nested/dirs').mkdir(parents=True, exist_ok=True)# 删除目录
Path('empty_dir').rmdir() # 必须为空# 递归删除目录 (使用shutil)
import shutil
shutil.rmtree(Path('dir_to_remove'))
3.3 文件操作
# 创建文件
Path('new_file.txt').touch()# 重命名/移动文件
Path('old.txt').rename('new.txt')
Path('file.txt').replace('backup/file.txt') # 原子性操作# 删除文件
Path('temp.txt').unlink(missing_ok=True) # Python 3.8+# 创建符号链接
target = Path('target_file')
link = Path('link_file')
link.symlink_to(target)
3.4 文件属性操作
p = Path('file.txt')# 获取/设置权限
print(oct(p.stat().st_mode)[-3:]) # 权限八进制表示
p.chmod(0o644) # 设置权限# 获取/设置时间戳
atime = p.stat().st_atime # 访问时间
mtime = p.stat().st_mtime # 修改时间
p.touch() # 更新修改时间为当前时间# 更改所有者 (Unix-like系统)
import os
p.chown(1000, 1000) # UID, GID
4. 目录遍历与模式匹配
4.1 目录遍历方法
# 遍历直接子项
for item in Path('.').iterdir():print(item.name)# 递归遍历
for item in Path('src').glob('**/*'):if item.is_file():print(item.relative_to('src'))
4.2 模式匹配技巧
# 查找所有Python文件
py_files = list(Path('.').glob('*.py'))# 递归查找所有测试文件
test_files = Path('tests').rglob('test_*.py')# 查找多个扩展名
code_files = Path('src').glob('*.[ch]') # C头文件和源文件# 查找隐藏文件
dot_files = Path('.').glob('.*')# 使用生成器表达式筛选
large_files = (p for p in Path('.').iterdir() if p.is_file() and p.stat().st_size > 1e6)
4.3 高级文件查找
from datetime import datetime, timedelta# 按修改时间查找
cutoff = (datetime.now() - timedelta(days=7)).timestamp()
recent_files = [p for p in Path('logs').iterdir()if p.is_file() and p.stat().st_mtime > cutoff
]# 按文件内容查找
def find_in_files(directory, pattern):for p in Path(directory).rglob('*'):if p.is_file() and pattern in p.read_text():yield p
5. 高级路径处理
5.1 路径解析与规范化
# 解析符号链接
link_path = Path('symlink').resolve()# 相对路径计算
try:rel_path = Path('/a/b/c').relative_to('/a')print(rel_path) # b/c
except ValueError:print("不是子路径")# 路径归一化
Path('a/./b/../c') # 自动归一化为 a/c
5.2 临时文件处理
import tempfile# 创建临时目录
with tempfile.TemporaryDirectory() as tmpdir:tmp_path = Path(tmpdir)temp_file = tmp_path / 'temp.txt'temp_file.write_text('临时内容')# 使用临时文件...# 创建命名临时文件
with tempfile.NamedTemporaryFile(delete=False) as tmp:temp_path = Path(tmp.name)temp_path.write_text('保留内容')
5.3 路径模式匹配
from fnmatch import fnmatch# 自定义匹配函数
def complex_match(path, patterns):return any(fnmatch(path.name, pattern) for pattern in patterns)# 使用自定义匹配
files = [p for p in Path('.').iterdir() if complex_match(p, ['*.txt', '*.md'])]
6. 跨平台开发
6.1 处理路径差异
# 平台无关的路径创建
data_path = Path('data') / 'dataset.csv'# 处理Windows路径分隔符
win_path = Path('C:\\Users\\Name')
posix_path = Path('/home/name')# 转换路径风格
if os.name == 'nt':path_str = str(win_path)
else:path_str = str(posix_path)
6.2 纯路径计算
# 跨平台路径计算
server_path = PurePosixPath('/server/data')
local_path = PureWindowsPath('C:/local/data')# 路径相对关系
if server_path.is_relative_to('/server'):relative = server_path.relative_to('/server')
6.3 路径格式转换
# 转换为URI
from urllib.parse import urljoin
uri = urljoin('file:', Path('data.txt').absolute().as_uri())# 转换为字符串
str_path = str(Path('file.txt'))
bytes_path = bytes(Path('file.txt')) # Python 3.10+
7. 性能优化
7.1 高效文件处理
# 处理大文件
with Path('large.log').open('r') as f:for line in f:process_line(line)# 使用内存映射
import mmap
with Path('huge.bin').open('rb') as f:with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mm:process_memory(mm)
7.2 减少系统调用
# 避免重复stat调用
path = Path('file.txt')
stat_info = path.stat()
size = stat_info.st_size
mtime = stat_info.st_mtime# 批量文件操作
with ThreadPoolExecutor() as executor:executor.map(process_file, Path('.').glob('*.dat'))
7.3 高效目录遍历
# 使用生成器避免内存占用
def find_large_files(directory, min_size):for p in directory.rglob('*'):try:if p.is_file() and p.stat().st_size > min_size:yield pexcept OSError: # 处理权限问题continue# 使用os.scandir底层API
def fast_dir_scan(path):with os.scandir(path) as entries:for entry in entries:yield Path(entry.path)
8. 与传统模块对比
8.1 与os/os.path对比
操作 | os/os.path | pathlib |
---|---|---|
路径拼接 | os.path.join('a', 'b') | Path('a') / 'b' |
获取文件名 | os.path.basename(p) | Path(p).name |
获取父目录 | os.path.dirname(p) | Path(p).parent |
路径存在 | os.path.exists(p) | Path(p).exists() |
是文件 | os.path.isfile(p) | Path(p).is_file() |
是目录 | os.path.isdir(p) | Path(p).is_dir() |
绝对路径 | os.path.abspath(p) | Path(p).absolute() |
8.2 与glob对比
操作 | glob | pathlib |
---|---|---|
简单匹配 | glob.glob('*.py') | list(Path().glob('*.py')) |
递归匹配 | glob.glob('**/*.py', recursive=True) | list(Path().rglob('*.py')) |
结果类型 | 字符串列表 | Path对象列表 |
Pathlib优势:
- 面向对象接口
- 方法链式调用
- 统一API接口
- 更好的异常处理
- 自动路径归一化
9. 最佳实践
9.1 优先使用Path对象
# 推荐:使用Path对象操作
config = Path.home() / '.config' / 'app.ini'
if config.exists():settings = config.read_text()# 不推荐:混合字符串和Path
config = os.path.join(os.path.expanduser('~'), '.config', 'app.ini')
9.2 利用链式调用
# 方法链示例
(Path('data').with_suffix('.csv').read_text(encoding='utf-8').splitlines().filter(lambda x: 'error' in x))
9.3 安全路径操作
# 检查路径存在再操作
config_path = Path.home() / '.config' / 'myapp'
if config_path.exists():for cfg in config_path.glob('*.conf'):process_config(cfg)
else:create_default_config(config_path)
9.4 处理路径注入
# 验证用户输入路径
def safe_open(user_input):base = Path('/safe/directory')requested = base / user_inputif base.resolve() not in requested.resolve().parents:raise ValueError("非法路径访问")return requested.open()
9.5 跨平台开发技巧
# 使用Path处理路径分隔符
data_file = Path('data') / 'dataset.csv'# 避免硬编码分隔符
if Path('/') in Path.cwd().parents:print("Unix-like系统")
9.6 高效文件处理
# 使用上下文管理器
with Path('data.json').open('r', encoding='utf-8') as f:data = json.load(f)# 处理大文件
def process_large_file(path):with path.open('rb') as f:while chunk := f.read(8192):process_chunk(chunk)
10. 常见问题解决
10.1 路径不存在错误
try:content = Path('missing.txt').read_text()
except FileNotFoundError:create_file('missing.txt')
10.2 权限问题处理
try:Path('restricted.txt').write_text('content')
except PermissionError:print("无写入权限,尝试使用sudo")
10.3 符号链接处理
# 解析符号链接
actual_path = Path('symlink').resolve()# 创建相对符号链接
Path('link').symlink_to(Path('../target'))
10.4 处理非常规文件名
# 处理带空格或特殊字符的文件名
weird_file = Path('file with spaces.txt')
content = weird_file.read_text()# 处理非UTF-8文件名
import sys
for p in Path('.').iterdir():try:print(p.name)except UnicodeEncodeError:print(p.name.encode(sys.getfilesystemencoding(), errors='replace'))
10.5 路径注入防御
# 防止目录遍历攻击
def safe_join(base, user_path):base = Path(base).resolve()target = base / user_pathif base.resolve() not in target.resolve().parents:raise ValueError("非法路径")return target