Python 的路径操作 pathlib

Ruijun Gao @ Dec 19, 2020

不会吧?不会吧?不会有人还在用 os.path 等模块来操作文件系统路径吧?难写又难读,看着就头痛。

今天我们将介绍 pathlib 模块。它同 os.path 模块一样也是 Python 标准库的一员。它提供了面向对象的路径操作,简洁易用、语义明确,让你真正体验到「人生苦短,我用 Python」的路径操作。

模块概览

pathlib 模块中最重要的内容就是 Path 类,我们可以简单地使用一个路径字符串构造它,例如 Path('~/Downloads')。同时它提供了许多易于理解和使用的方法。这里将不逐一说明,使用时可在官方文档检索方法的详细说明。以下提供了它们与命令式风格的模块提供的函数的简单对照,但功能并不完全一致,可供参考。

构造操作

以下操作可以用于构造 Path 实例。

legacypathlib
os.path.join()PurePath.joinpath(), PurePath.__truediv__()
os.getcwd()Path.cwd()
os.path.expanduser()Path.expanduser(), Path.home()
os.path.abspath()Path.resolve()
os.readlink()Path.readlink()
os.listdir()Path.iterdir()
glob.glob(), glob.iglob()Path.glob(), Path.rglob()
N/APath.with_name(), Path.with_stem(), Path.with_suffix()

查询操作

以下操作可以用于查询 Path 实例对应的文件或目录的相关属性。

legacypathlib
os.path.basename()PurePath.name, PurePath.stem
os.path.dirname()PurePath.parent, PurePath.parents
os.path.splitext()PurePath.suffix, PurePath.suffixes
os.path.isabs()PurePath.is_absolute()
os.path.isdir()Path.is_dir()
os.path.isfile()Path.is_file()
os.path.islink()Path.is_symlink()
os.path.samefile()Path.samefile()
os.path.exists()Path.exists()
os.stat()Path.stat(), Path.lstat(), Path.owner(), Path.group()

更改操作

以下操作可以用于更改(包括创建、修改和删除)Path 实例对应的文件或目录。

legacypathlib
os.mkdir(), os.makedirs()Path.mkdir()
os.link()Path.link_to()
os.symlink()Path.symlink_to()
os.rename()Path.rename()
os.replace()Path.replace()
os.chmod()Path.chmod(), Path.lchmod()
os.rmdir()Path.rmdir()
os.remove(), os.unlink()Path.unlink()
N/APath.touch()

输入输出操作

以下操作可以用于对(包括创建、修改和删除)Path 实例对应的文件进行输入输出。

legacypathlib
open()Path.open()
open().read()Path.read_bytes(), Path.read_text()
open().write()Path.write_bytes(), Path.write_text()

实战演练

在这一部分,我们将在现实使用场景中使用 pathlib 做一些有实际意义的事情,以展示其强大的功能。

收集图片

from itertools import chain
from pathlib import Path

def collect_files_to_lines(source_folder, target_file, formats):
    source_folder = Path(source_folder)
    file_paths = (source_folder.rglob(f'*.{f}') for f in formats)
    file_paths = sorted(chain.from_iterable(file_paths))
    Path(target_file).write_text('\n'.join(map(str, file_paths)))
    return len(file_paths)

if __name__ == '__main__':
    num_images = collect_files_to_lines(
        'images', 'images.txt', ['png', 'jpg', 'gif', 'bmp'])
    print(f'{num_images = }')

collect_images.py 脚本将收集 images 目录下的所有以 pngjpggifbmp 为后缀的图片文件,按字典序排序后写入 images.txt 文件中。使用了 rglobwrite_text 方法。

拷贝文件

from itertools import chain
from pathlib import Path
import shutil

def copy_files_to_folder(source_folder, target_folder, formats):
    source_folder, target_folder = Path(source_folder), Path(target_folder)
    file_paths = (source_folder.rglob(f'*.{f}') for f in formats)
    file_paths = sorted(chain.from_iterable(file_paths))
    for source_path in file_paths:
        relative_path = source_path.relative_to(source_folder)
        target_path = target_folder.joinpath(relative_path)
        target_path.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy(source_path, target_path)
    return len(file_paths)

if __name__ == '__main__':
    num_copied = copy_files_to_folder('source', 'target', ['txt'])
    print(f'{num_copied = }')

copy_files.py 脚本将收集 source 目录下的所有以 txt 为后缀的文件,在保持原来的目录结构的情况下拷贝到 target 目录下。

脚本中使用了 relative_to 方法来获取相对路径以及 mkdir 方法的 parentsexist_ok 参数来方便地创建目录结构。另外,target_folder.joinpath(relative_path) 也可以等价地写成 target_folder / relative_path

随着 Python 版本的迭代,很多如 shutil.copy 这样接受路径字符串的函数,甚至包括一些第三方包(如 NumPy 和 Pillow)中的某些函数(如 numpy.savenumpy.loadPIL.Image.open),现在也可以接受路径对象了。