pathlib-优雅的解决文件路径

1.pathlib的设计哲学

  传统的路径主要依赖于os.path​ 模块中的函数(如 os.path.join​, os.path.dirname​, os.path.exists​)来处理路径。这种方式是函数式的,将路径视为字符串

  传统方式 (os.path) 的问题:

import os

path = "/home/user/data/file.txt"
dir_name = os.path.dirname(path) # '/home/user/data'
base_name = os.path.basename(path) # 'file.txt'
joined_path = os.path.join(dir_name, "new_file.txt") # '/home/user/data/new_file.txt'

if os.path.exists(joined_path):
    # ...

  这种方式繁琐,需要记住很多函数名,并且不同操作系统路径分隔符(/ vs \)的问题需要开发者自己处理(尽管 os.path.join​ 会帮忙)。

  这很不Pythonic,不够优雅(还在用传统模块?来试试pathlib)

  ​pathlib的解决方案:
pathlib​ 将文件系统路径视为一个对象,而不是字符串。它提供了一个面向对象的 API,通过方法属性来操作路径。代码更加清晰、易读,符合Python的“对象思考”模式。

from pathlib import Path

path = Path("/home/user/data/file.txt")
dir_name = path.parent # 获取父目录,是一个Path对象
base_name = path.name # 获取文件名(带后缀)
joined_path = dir_name / "new_file.txt" # 使用 / 操作符拼接路径

if joined_path.exists():
    # ...

  ‍

2. Path 和 PurePath

  1. PurePath(纯路径类)

    • 作用:只负责对路径字符串进行操作(如拼接、分解、提取各部分)。它不接触真正的文件系统。
    • 为什么需要它?当你需要在不存在的路径上执行操作,或者你的代码需要在不同平台(Windows/Linux)上解析和操作路径时,它非常有用。它更快,因为它不进行任何IO操作。
    • 子类:PurePosixPath​, PureWindowsPath​;这里在不同的系统中,使用的子类不一致
  2. Path(具体路径类)

    • 作用:继承自 PurePath​。它除了具备纯路径的所有功能,还提供了与真实文件系统交互的方法(如检查存在性、读写文件、创建删除目录等)。
    • 你绝大多数时候都会使用这个类。
    • 它根据当前操作系统自动选择使用 WindowsPath​ 或 PosixPath​。

  ‍

  简单记法:Path= PurePath+ 文件系统操作

  ​PurePath​的操作不涉及文件系统,仅对字符串进行操作;它只关心路径字符串本身的规则和操作。它是一个抽象的概念。

  即,

  • PurePath不知道也不关心

    • 当前是什么操作系统?
    • 这个路径在磁盘上真的存在吗?
    • 我有权限访问它吗?
  • 为什么需要它

    • 效率:因为没有IO操作,所以速度极快。
    • 效用:你可以在任何地方使用它,即使路径不存在。比如,你想计算一个准备保存的文件的最终路径:download_path = Path(' /downloads') / generate_unique_filename()​,这里你根本不需要文件存在。
    • 跨平台路径处理:如果你在Linux上编写代码,但需要处理一个Windows路径字符串(比如解析一个从Windows服务器发来的日志文件路径),你可以直接使用 PureWindowsPath​,而不会因为平台不匹配而出错。

  ‍

3. 基础用法和常见操作

3.1 导入和创建 Path 对象

from pathlib import Path

# 创建Path对象:表示当前目录
p = Path() # 相对路径
p = Path('.') # 同上

# 创建绝对路径
p = Path('/home/user/docs') # Linux/macOS
p = Path('C:/Users/Name/Docs') # Windows,推荐使用正斜杠
p = Path(r'C:\Users\Name\Docs') # Windows,使用原始字符串和反斜杠

# 通过拼接创建(使用 / 操作符,非常直观!)
base_dir = Path('/home/user')
sub_dir = base_dir / 'data' / 'project_a'
print(sub_dir) # /home/user/data/project_a

# 连接字符串列表
parts = ['home', 'user', 'file.txt']
p = Path('/').joinpath(*parts)

3.2 获取路径的各个组成部分(属性)

  假设 p = Path('/home/user/data/report.txt')

属性 返回值 示例结果 类型
p.anchor 根目录(盘符) ‘/’ 或 ‘C:’ str
p.parent 父目录路径 Path(‘/home/user/data’) Path
p.parents 所有父目录的迭代器 索引 [0] 是直接父目录
p.name 完整文件名(带后缀) ‘report.txt’ str
p.stem 文件名(不带后缀) ‘report’ str
p.suffix 文件扩展名(最后一个点之后) ‘.txt’ str
p.suffixes 所有扩展名列表(对于 .tar.gz) [‘.tar’, ‘.gz’] list
p.parts 将路径分割为元组 (‘/’, ‘home’, ‘user’, ‘data’, ‘report.txt’) tuple

  例如:

p = Path('/home/user/data/archive.tar.gz')
print(p.parent)   # /home/user/data
print(p.name)     # archive.tar.gz
print(p.stem)     # archive.tar
print(p.suffix)   # .gz
print(p.suffixes) # ['.tar', '.gz']

# 遍历所有父目录
for parent in p.parents:
    print(parent)
# /home/user/data
# /home/user
# /home
# /

3.3 文件系统操作(判断、创建、删除)

  这些方法需要路径在文件系统中真实存在。

p = Path('my_file.txt')

# 判断
p.exists()   # 路径是否存在
p.is_file()  # 是否是文件
p.is_dir()   # 是否是目录
p.is_absolute() # 是否是绝对路径

# 文件信息(如果文件不存在会抛出异常)
p.stat()            # 获取文件信息(如大小、修改时间),返回os.stat_result对象
p.stat().st_size    # 文件大小(字节)
p.stat().st_mtime   # 最后修改时间(时间戳)

# 创建
p.mkdir()           # 创建目录,parents=True可创建父目录(类似mkdir -p),exist_ok=True避免目录已存在的错误
p.mkdir(parents=True, exist_ok=True)
p.touch()           # 创建空文件,exist_ok=True可忽略文件已存在的错误

# 删除
p.unlink()          # 删除文件(missing_ok=True可忽略文件不存在的错误,Python 3.8+)
p.unlink(missing_ok=True)
p.rmdir()           # 删除**空**目录
# 注意:删除非空目录需要用到shutil.rmtree,pathlib本身不提供该功能

3.4 文件读写

  ​Path​ 对象提供了几个便捷的方法来简化常见的文件操作。

p = Path('message.txt')

# 写入数据(覆盖写入)
p.write_text('Hello, Pathlib!') # 返回写入的字符数
p.write_bytes(b'Binary data')    # 写入字节数据

# 读取数据
content = p.read_text(encoding='utf-8') # 返回字符串
binary_data = p.read_bytes()            # 返回字节串

# 打开文件(对于复杂操作,仍然需要open)
with p.open(mode='r', encoding='utf-8') as f:
    content = f.read()

  ‍

3.5 查找文件(通配查找 – Glob Patterns)

  这是 pathlib​ 非常强大的功能,用于查找匹配特定模式的文件。

  • glob(pattern)​:在当前目录下递归查找,只找一层
  • rglob(pattern)​:在当前目录及其所有子目录中递归查找(Recursive Glob)。

  常用通配符:

  • *​:匹配任何非分隔符字符(在POSIX系统上不包括 /​,在Windows上不包括 \​)
  • **​:匹配任何字符,包括分隔符(用于递归匹配,但在 glob​ 中需要单独使用)
  • ?​:匹配任何单个字符
  • [abc]​:匹配括号内的任何一个字符

  例如:

base_dir = Path('/home/user/project')

# 查找当前目录下所有的.py文件
for py_file in base_dir.glob('*.py'):
    print(py_file)

# 递归查找项目目录下所有的Markdown文件
for md_file in base_dir.rglob('*.md'):
    print(md_file)

# 查找所有以test开头的.py文件
for test_file in base_dir.glob('test*.py'):
    print(test_file)

# 更复杂的例子:递归查找所有以data开头,扩展名为.csv或.txt的文件
for data_file in base_dir.rglob('data*.*'):
    if data_file.suffix in ['.csv', '.txt']:
        print(data_file)

# 使用 ** 的模式 (通常在glob中,rglob('pattern') 等价于 glob('**/pattern'))
list(base_dir.glob('**/*.py')) # 等同于 base_dir.rglob('*.py')

  ‍

3.6 路径解析和修改(方法)

p = Path('/home/user/old_name.txt')

# 获取绝对路径
abs_p = p.resolve() # 如果路径不存在,resolve()仍然会尽力解析,但不会检查文件是否存在

# 将路径转换为URI格式
uri = p.as_uri() # 'file:///home/user/old_name.txt'

# 将相对路径转换为绝对路径(基于当前工作目录)
p = Path('my_file.txt')
abs_p = p.absolute()

# 重命名/移动文件
new_p = p.rename('/home/user/new_name.txt')
# 通常与父目录一起使用
new_p = p.with_name('new_name.txt') # 返回一个新Path对象,只改名字
p.rename(new_p) # 执行实际的重命名操作

# 修改扩展名
new_p = p.with_suffix('.pdf') # /home/user/old_name.pdf

  ​with_xxx​ 方法非常有用,它们会返回一个修改了特定部分的新 Path​ 对象,而不会改变原对象。

  ‍

文末声明:

您必须遵守关于,您可以随意转发/引用,但要注明原作者Leon或设置本文跳转连接,并且您必须在文中包含或提醒浏览者遵守作者声明
欢迎关注公众号获取第二手文章!高效工作法

暂无评论

发送评论 编辑评论


				
上一篇