📑 目录
- 一、Pathlib 简介
- 二、核心 API 详解
- 2.1 Path() - 创建路径对象
- 2.2 iterdir() - 遍历目录内容
- 2.3 name 属性 - 获取路径的最后组成部分
- 2.4 exists() - 检查路径是否存在
- 2.5 mkdir() - 创建目录
- 2.6 parent 属性 - 获取父目录
- 2.7 路径拼接(/ 运算符)
- 2.8 小结:整个处理流程中的 Path 对象流转
- 三、完整示例代码
- 四、总结
一、Pathlib 简介
在 Python 中处理文件路径,传统方式通常使用os.path模块,虽然功能完整,但存在一些不够优雅的地方:路径拼接使用字符串操作容易出错,跨平台兼容性需要额外处理,代码可读性也较差。
Python 3.4 引入了pathlib模块,提供了面向对象的文件系统路径操作方式。Path类是pathlib的核心,它将路径表示为对象而非字符串,提供了直观的方法来处理路径、文件和目录。
Pathlib 的优势
- 面向对象:路径作为对象,方法调用更自然
- 跨平台:自动处理不同操作系统的路径分隔符
- 链式调用:方法可以链式组合,代码更简洁
- 功能完整:集成了文件系统操作、路径解析、文件读写等丰富功能
二、核心 API 详解
下面结合我们批量处理图片文件的实例,逐一讲解用到的 Path API。我们的程序需要完成以下任务:扫描指定目录下所有以sam3_seg_all_为前缀的子目录,将每个子目录中的seg_all.jpg文件复制到统一的seg_all目录,并按{id}_seg_all.jpg格式重命名。
2.1 Path() - 创建路径对象
frompathlibimportPath# 从当前文件获取所在目录base=Path(__file__).parent# 从字符串创建base=Path("/home/user/project")# 从命令行参数创建base=Path(sys.argv[1])Path()是pathlib的核心构造函数,可以接受字符串、路径片段或特殊变量来创建 Path 对象。
__file__是 Python 内置变量,表示当前脚本文件的绝对路径。Path(__file__).parent获取脚本所在的目录,这比使用os.path.dirname(os.path.abspath(__file__))要简洁得多。- 创建 Path 对象后,我们可以调用各种方法进行路径操作,而无需像字符串操作那样担心路径分隔符的问题。
在我们的程序中,首先通过Path(__file__).parent获取脚本所在目录作为基础目录,或者通过sys.argv[1]获取用户指定的目录,再使用Path()将其转换为 Path 对象供后续操作使用。
2.2 iterdir() - 遍历目录内容
defget_dirs(base_dir):return[dfordinbase_dir.iterdir()ifd.is_dir()]iterdir()是 Path 对象的方法,返回目录下所有文件和子目录的迭代器。与os.listdir()不同,iterdir()返回的是 Path 对象而非字符串,这意味着我们无需再拼接路径就能直接对子项进行操作。
关键点:
base_dir是一个 Path 对象,代表我们想要遍历的目录- 循环变量
d是 Path 对象,代表目录中的每个子项(文件或目录) d.is_dir()检查当前子项是否为目录,返回布尔值- 结合列表推导式,我们一次性筛选出所有子目录
对比传统的os.listdir()方式:
# 传统方式importos dirs=[os.path.join(base_dir,d)fordinos.listdir(base_dir)ifos.path.isdir(os.path.join(base_dir,d))]# Pathlib 方式dirs=[dfordinbase_dir.iterdir()ifd.is_dir()]Pathlib 方式不仅代码更短,而且每个d已经是一个完整的 Path 对象,可以直接用于后续操作。
2.3 name 属性 - 获取路径的最后组成部分
dir_name=d.name# 获取目录名,如 "sam3_seg_all_219"name属性返回路径的最后一部分,相当于os.path.basename()。对于目录路径,返回目录名;对于文件路径,返回文件名。
在我们的代码中:
fordinget_dirs(base_dir):dir_id=extract_id(d.name)# d.name 是 "sam3_seg_all_219"这里的d是 Path 对象,代表一个子目录,d.name获取这个目录的名称(字符串),然后传入extract_id()函数提取数字 ID。
使用name属性的优势是无需额外调用os.path.basename(),直接通过属性访问即可。
2.4 exists() - 检查路径是否存在
ifnotsrc.exists():print("文件不存在")exists()方法检查 Path 对象所代表的路径在文件系统中是否真实存在。返回True或False。它适用于文件和目录,比os.path.exists()更简洁易用。
在我们的程序中,有两处使用了exists():
1. 检查源文件是否存在:
src=d/"seg_all.jpg"ifnotsrc.exists():results.append((d.name,"源文件不存在"))continue这里src是 Path 对象,代表d目录下的seg_all.jpg文件。调用src.exists()检查该文件是否存在,如果不存在则记录错误并跳过。
2. 检查目标文件是否已存在:
ifdst.exists():returnFalse,"已存在"在copy_file()函数中,dst是目标文件路径,调用dst.exists()检查是否已经存在同名文件,避免重复复制造成覆盖。
2.5 mkdir() - 创建目录
target.mkdir(exist_ok=True)mkdir()方法用于创建目录,参数exist_ok=True表示如果目录已经存在,不抛出FileExistsError异常,行为类似 Shell 命令中的mkdir -p。
在我们的程序中:
defget_target_dir(base_dir):target=base_dir/"seg_all"target.mkdir(exist_ok=True)returntarget这里的target是 Path 对象,代表base_dir/seg_all路径。调用target.mkdir(exist_ok=True)确保目录存在:
- 如果
seg_all目录不存在,则创建它 - 如果
seg_all目录已存在,不做任何事
这种 “确保目录存在” 的写法非常常用,比先判断再创建要简洁得多。如果没有exist_ok=True,当目录已存在时会抛出异常,需要额外的异常处理。
对比传统方式:
# 传统方式importosifnotos.path.exists(target_path):os.makedirs(target_path)# Pathlib 方式target.mkdir(exist_ok=True)2.6 parent 属性 - 获取父目录
parent_dir=src.parent# 获取文件的父目录parent属性返回路径的父目录,相当于os.path.dirname()。可以连续使用多个.parent来获取更上层的目录。
在我们的代码中虽然没有直接使用parent,但在理解路径结构时很有用。例如:
src=d/"seg_all.jpg"parent_dir=src.parent# 这个 parent_dir 实际上就是 dprint(parent_dir==d)# True当我们需要获取文件所在目录、或者获取上级目录时,parent属性非常方便:
# 获取上级目录的上级目录grandparent=src.parent.parent# 获取同目录下的其他文件other_file=src.parent/"other.jpg"2.7 路径拼接(/ 运算符)
src=d/"seg_all.jpg"dst=target_dir/f"{dir_id}_seg_all.jpg"/运算符是 Path 最优雅、最核心的特性之一。它用于拼接路径,会自动处理不同操作系统的路径分隔符(Windows 下是\,Linux/macOS 下是/)。
在我们的程序中,/运算符在两处被使用:
第一处:构建源文件路径
src=d/"seg_all.jpg"这里的d是一个 Path 对象,代表一个目录,例如"/home/user/sam3_seg_all_219"。通过/运算符拼接"seg_all.jpg",得到src为Path("/home/user/sam3_seg_all_219/seg_all.jpg")。
整个过程是:
d是 Path 对象,通过get_dirs()获得,代表sam3_seg_all_219目录d / "seg_all.jpg"返回新的 Path 对象,指向该目录下的seg_all.jpg文件- 这个拼接操作不会修改原有的
d,而是创建新的 Path 对象
第二处:构建目标文件路径
dst=target_dir/f"{dir_id}_seg_all.jpg"这里的target_dir是 Path 对象,代表seg_all目录。dir_id是从目录名中提取的数字 ID(如"219")。f"{dir_id}_seg_all.jpg"生成字符串"219_seg_all.jpg"。
拼接后的dst为Path("/home/user/seg_all/219_seg_all.jpg")。
为什么说 / 运算符优雅?
传统方式需要这样写:
# 传统 os.path 方式importos src=os.path.join(d,"seg_all.jpg")# 或者使用字符串拼接src=d+"/"+"seg_all.jpg"# 不跨平台Pathlib 的/运算符:
- 支持 Path 对象与字符串拼接
- 也支持两个 Path 对象拼接
- 自动处理多余的
/,不会出现//或路径末尾缺少/的问题 - 跨平台兼容,Windows 下自动使用
\
理解变量类型:
d:Path 对象,代表一个子目录src:Path 对象,代表源文件路径dst:Path 对象,代表目标文件路径target_dir:Path 对象,代表统一存放文件的目录
2.8 小结:整个处理流程中的 Path 对象流转
为了更清晰理解 Path 对象在程序中的流转,我们来看看完整的处理流程:
用户输入基础目录 ↓ base = Path("/home/user/project") # Path 对象 ↓ get_dirs(base) ↓ iterdir() 遍历 → [Path("sam3_seg_all_219"), Path("sam3_seg_all_220")] ↓ d = Path("sam3_seg_all_219") # 每个子目录是 Path 对象 ↓ d.name → "sam3_seg_all_219" # 获取目录名(字符串) ↓ extract_id() → "219" # 提取 ID ↓ src = d / "seg_all.jpg" # 源文件路径:Path("sam3_seg_all_219/seg_all.jpg") ↓ src.exists() 检查文件是否存在 ↓ dst = target_dir / "219_seg_all.jpg" # 目标文件路径:Path("seg_all/219_seg_all.jpg") ↓ shutil.copy2(src, dst) # 执行复制整个过程中,Path 对象无缝地从目录遍历、文件名获取、路径拼接、存在性检查到最终的复制操作,全程保持类型安全,无需进行字符串到路径的转换。
三、完整示例代码
#!/usr/bin/env python3""" 批量处理 sam3_seg_all_* 目录中的 seg_all.jpg 文件 将文件重命名为 {id}_seg_all.jpg 并统一收集到 seg_all 目录 """importreimportshutilfrompathlibimportPathimportsysdefget_dirs(base_dir):""" 获取指定目录下的所有子目录 Args: base_dir: Path 对象,基础目录 Returns: list: Path 对象列表,所有子目录 """# iterdir() 返回目录内容迭代器,is_dir() 过滤出目录return[dfordinbase_dir.iterdir()ifd.is_dir()]defextract_id(dir_name):""" 从目录名中提取数字 ID Args: dir_name: 字符串,目录名 Returns: str or None: 提取的 ID,不匹配则返回 None """# re.match 匹配前缀格式,group(1) 提取数字部分match=re.match(r'^sam3_seg_all_(\d+)$',dir_name)returnmatch.group(1)ifmatchelseNonedefget_target_dir(base_dir):""" 获取目标目录,不存在则创建 Args: base_dir: Path 对象,基础目录 Returns: Path: 目标目录路径 """# 使用 / 操作符拼接路径target=base_dir/"seg_all"# mkdir 创建目录,exist_ok=True 避免目录已存在时报错target.mkdir(exist_ok=True)returntargetdefcopy_file(src,dst):""" 复制文件,如果目标已存在则跳过 Args: src: Path 对象,源文件路径 dst: Path 对象,目标文件路径 Returns: tuple: (bool, str) 是否成功及状态信息 """# exists() 检查目标文件是否已存在ifdst.exists():returnFalse,"已存在"# shutil.copy2 复制文件并保留元数据shutil.copy2(src,dst)returnTrue,"成功"defprocess(base_dir):""" 主处理函数 Args: base_dir: Path 对象,要处理的根目录 Returns: tuple: (list, Path) 处理结果列表和目标目录 """# 获取目标目录target_dir=get_target_dir(base_dir)results=[]# 遍历所有子目录fordinget_dirs(base_dir):# 从目录名提取 IDdir_id=extract_id(d.name)# name 属性获取目录名ifnotdir_id:continue# 构建源文件路径,使用 / 操作符拼接src=d/"seg_all.jpg"# exists() 检查源文件是否存在ifnotsrc.exists():results.append((d.name,"源文件不存在"))continue# 构建目标文件路径,使用 f-string 生成新文件名dst=target_dir/f"{dir_id}_seg_all.jpg"# 执行复制success,msg=copy_file(src,dst)results.append((d.name,msg))returnresults,target_dirdefmain():""" 程序入口 """# 获取基础目录:命令行参数或脚本所在目录# Path(__file__).parent 获取脚本所在目录base=Path(sys.argv[1])iflen(sys.argv)>1elsePath(__file__).parentprint(f"处理目录:{base}")# 执行处理results,target=process(base)# 输出结果print(f"\n目标目录:{target}")forname,msginresults:print(f"{name}:{msg}")# 统计信息success=sum(1for_,msginresultsifmsg=="成功")skipped=sum(1for_,msginresultsifmsg=="已存在")failed=len(results)-success-skippedprint(f"\n总计:{len(results)}个目录")print(f"成功:{success}, 跳过:{skipped}, 失败:{failed}")if__name__=="__main__":main()四、总结
通过这个实际案例,我们看到了 Pathlib 如何让文件路径处理变得简洁优雅:
| 操作 | Pathlib 方式 | 传统 os.path 方式 |
|---|---|---|
| 获取脚本所在目录 | Path(__file__).parent | os.path.dirname(os.path.abspath(__file__)) |
| 遍历目录 | base_dir.iterdir() | os.listdir(base_dir) |
| 拼接路径 | dir / "file.jpg" | os.path.join(dir, "file.jpg") |
| 获取文件名 | path.name | os.path.basename(path) |
| 检查存在性 | path.exists() | os.path.exists(path) |
| 创建目录 | path.mkdir(exist_ok=True) | os.makedirs(path, exist_ok=True) |
| 获取父目录 | path.parent | os.path.dirname(path) |
相比传统的os.path方式,Pathlib 将路径操作从字符串操作提升为对象操作,代码更清晰、更安全、更易维护。
推荐使用场景:
- 需要大量路径拼接和操作的脚本
- 需要跨平台兼容的项目
- 追求代码可读性和简洁性的开发
如果你的 Python 版本 >= 3.4,强烈推荐使用 Pathlib 处理文件路径相关任务。对于更复杂的文件系统操作,Pathlib 还提供了更多实用的方法,如glob()、rglob()、rename()、unlink()等,值得深入学习。