Skip to content

pathlib

pathlib 模块提供了表示文件系统路径的类,并且对路径的抽象用于兼容不同的操作系统。

模块架构

pathlib-inheritance

上图就是 pathlib 的模块架构。整个操作系统包含两种类型的路径:

  1. PosixPath: 用于 MacOS、Linux 等类 Unix 操作系统,他们的特点就是 / 是根目录,而路径通过 / 分割
  2. WindowsPath: 用于 Windows 操作系统,他使用 C:\ 作为操作符, 而且使用 \ 作为路径的分割

该模块屏蔽了他们之间的区别提供了两种路径抽象:

  • PurePath: 纯路径
  • Path: 真实路径

之所以要抽象一个纯路径是为了兼容性,下面的几种用例中就比较适合使用纯路径:

  1. 如果要在 Unix 设备上操作 Windows 路径(或者相反),此时就可以使用纯路径来实现
  2. 如果只想操作路径对象而不想实际访问系统(通常是一个 URL 类的路径),此时就可以实例化一个纯路径

Note

在大多数时候都只需要构造 Path 对象就好了。它会自动根据当前操作系统来调用 PosixPath 或 WindowsPath 来构造路径对象。

Tips

更具体的实现思路可以参看PEP 428: pathlib 模块PEP 355: Path,PEP355 被拒绝了,他 PEP428 是他的替代

路径构成

Python
┌─────────────────────┬────────────┐
          dir            base    
├──────┬              ├──────┬─────┤
 root                name  ext 
"  /    home/user/dir / file  .txt "
└──────┴──────────────┴──────┴─────┘

# 上图是 nodejs Path 中的路径分割,Python 的也大致如此不过他们的名字有区别
Nodejs Python 说明
root anchor 驱动器和根的联合
- drive 驱动器
- root
dir patent 父路径
base name 路径最后组件的字符串
name stem 最后组件去除后缀
ext suffix 后缀
Python
# 驱动器算是 Windows 特有的
PureWindowsPath('c:/Program Files/').drive # 'c:'
PureWindowsPath('/Program Files/').drive # ''
PurePosixPath('/etc').drive # ''
# UNC 共享也被认为是驱动器
PureWindowsPath('//host/share/foo.txt').drive # '\\\\host\\share'


# root 表示根路径
PureWindowsPath('c:/Program Files/').root  # '\\'
PurePosixPath('/etc').root  # '/'
PureWindowsPath('//host/share').root # '\\'

# anchor 是驱动器和根路径的集合

# patent 用于表示符路径
PurePosixPath('/a/b/c/d').patent  # PurePosixPath('/a/b/c')
# 父路径不能超过 anchor 或空路径
PurePosixPath('/').parent  # PurePosixPath('/')
PurePosixPath('.').parent  # PurePosixPath('.')
# 还有一个比较特殊的 patents 来返回祖先列表
p = PureWindowsPath('c:/foo/bar/setup.py')
p.parents[0] # PureWindowsPath('c:/foo/bar')
p.parents[1] # PureWindowsPath('c:/foo')
p.parents[2] # PureWindowsPath('c:/')

# name 表示路径的最后一个组件(不一定是文件)
PurePosixPath('my/library/setup.py').name  # 'setup.py'
# 不能超过 anchor
PureWindowsPath('//some/share').name # ''

# name 还能够拆封为 stem 和 suffix
# 只返回最后一个后缀,有一个 suffixes 会返回所有后缀
PurePosixPath('my/library.tar.gz').suffix  # '.gz'
PurePosixPath('my/library.tar.gar').suffixes  # ['.tar', '.gar']
# stem 同样只剔除最后一个后缀
PurePosixPath('my/library.tar.gz').stem # 'library.tar'

修改特定组件

除了通过 / 来拼接路径外,pathlib 还提供了几个方法来修改组件:

方法 说明
Path.joinpath(*pathsegments) 等价于 / 运算符
Path.with_name(name) 返回一个新的路径并修改 name,需要注意如果原始路径没有 name 会抛出 ValueError
Path.with_stem(stem) 返回一个带有修改后 stem 的新路径,注意如果原始路径没有 name 会抛出 ValueError
Path.with_suffix(suffix) 返回一个带有修改后的 suffix 的新路径,如果原来的没有后缀,新的后缀会添加,如果是空字符串则原有路径被删除

路径对象都是 hashable 的,所有修改的方法都是返回一个新的路径对象。

通过 / 运算符来拼接路径

Path 还将 / 操作符进行了重载,允许直接拼接路径:

Python
p = PurePath('/etc')
# PurePosixPath('/etc')
p / 'init.d' / 'apache2' # 直接使用 / 来进行拼接
# PurePosixPath('/etc/init.d/apache2')
q = PurePath('bin')  # 相对路径
'/usr' / q
# PurePosixPath('/usr/bin')
p / '/an_absolute_path'  # 两个绝对路径进行拼接,后者覆盖前者
# PurePosixPath('/an_absolute_path')
PureWindowsPath('c:/Windows', '/Program Files')
# PureWindowsPath('c:/Program Files')

相对路径处理

pathlib 支持相对路径,他提供了判断、获取相对路径的相关方法:

Python
# 判断是否是相对路径
PurePosixPath('/a/b').is_absolute() # True
# 判断路径是否相对于某个路径
p = PurePath('/etc/passwd')
p.is_relative_to('/etc') # True
p.is_relative_to('/usr') # False

# 计算此路径相对于某个路径的相对路径,如果不能计算引发 ValueError
# 返回的结果是一个相对路径
p = PurePosixPath('/etc/passwd')
p.relative_to('/') # PurePosixPath('etc/passwd')
p.relative_to('/etc') # PurePosixPath('passwd')
p.relative_to('/usr') # ValueError

特殊路径扩展

有些相对路径会包含 ...~ (这个不应该算相对路径)这样的符号,他们需要调用特定的函数来返回绝对路径:

Python
# 对于 ~
PosixPath('~/films/Monty Python').expanduser() # PosixPath('/home/eric/films/Monty Python')

# 将路径绝对化,解析任何符号
p = Path() # PosixPath('.')
p.resolve() # PosixPath('/home/antoine/pathlib')

# 会相对于工作目录并解析任何符号来返回绝对路径
Path('docs/../setup.py').resolve() # PosixPath('/home/antoine/pathlib/setup.py')

还有两个命令来获取特殊目录:

Python
# 返回家目录
Path.home() # PosixPath('/home/antoine')
# 返回当前目录,和  os.getcwd() 一样,不过返回 Path 而不是字符串
Path.cwd() # PosixPath('/home/antoine/pathlib')

文件接口

我们构建路径的核心就是用于定位操作系统中的文件,Path 上绑定了一些方法来更方便的对文件进行增删改查。

文件属性

使用 Path.stat() 能够返回文件的属性,他的返回值是 os.stat_result 对象,其中主要包括:

属性 说明
st_mode 文件权限
st_uid 文件所有者的用户 ID
st_gid 文件所有者的用户组 ID
st_size 文件大小(字节单位)
st_rsize 文件实际大小(字节单位)
st_rsize 文件实际大小(字节单位)
st_atime 最近访问时间时间戳
st_mtime 最近修改时间时间戳
st_birthtime 最近创建时间时间戳
st_fstype 文件所在的文件系统类型
Python
p = Path('setup.py')
p.stat().st_size # 956
p.stat().st_mtime # 1327883547.852554

判断路径存在

有一个统一的 Path.exists() 来判断路径是否指向一个已经存在的文件或目录:

Python
Path('.').exists() #True
Path('setup.py').exists() #True
Path('/etc').exists() #True
Path('nonexistentfile').exists() #False

Path 还提供了一系列的 is_xxx() 方法来判断指定路径是否是对应类型文件:

  • Path.is_file(): 判断当前路径是否是一个存在的文件
  • Path.is_dir(): 判断当前路径是否是一个存在的目录

创建文件或目录

他们对应了 shell 中的 touch 和 mkdir 命令:

  • Path.touch(mode=0o666, exist_ok=True): 创建文件,mode 指定文件全新,exist_ok=True 会在文件存在时更新其修改时间,如果为 False 文件存在时引发 FileExistsError 异常,通常不怎么用这个方法,更多的是直接 Path 提供的IO 接口
  • Path.mkdir(mode=0o777, parents=False, exist_ok=False): 创建目录,如果 patents=True 则父路径不能存在时创建(即 -p 递归创建)

还有一个比较特殊的就是创建连接:

  • Path.symlink_to(target:Path|str): 创建一个指向 target 的符号连接
  • Path.hardlink_to(target:Path|str): 创建一个指向 target 的硬连接

修改文件和目录

修改包括重命名、修改权限、还有删除:

  • Path.unlink(missing_ok=False): 删除文件或符号连接,如果路径指向目录等价于Path.rmdir()
  • Path.rmdir(): 移除空目录
  • Path.chmod(mode): 修改权限
  • Path.rename(target): 根据 target 重命名,这个相当于移动命令,在类 Unix 如果 target 存在则默认替换,在 Windows 下如果文件存在则引发 FileExistsError
  • Path.replace(target): 根据 target 移动,他会被默认替换

Tips

rename 和 replace 在 Linux 下仅仅是语意的不同。target 还可以是相对路径,他会使用当前工作目录补全。

遍历当前目录

Path 同样提供了类似于 ls 这样的命令,来读取目录下的所有文件:

  • Path.iterdir(): 返回路径下的所有文件的迭代器,条目顺序是任意产生的,不包含特殊的 ...,如果该路径不是一个目录会引发 OSError
  • Path.glob(pattern): 解析相对于 pattern 通配符的路径
  • Path.rglob(pattern): 相当于 Path.glob 中的通配符以 **/ 开头,即递归获取

glob 支持通配符获取文件,他由fnmatch模块的类 Unix 的通配符风格:

  • *: 匹配所有
  • ?: 匹配单个字符
  • [seq]: 匹配 seq 中的任意字符
  • [!seq]: 匹配不在 seq 中的任意字符
  • **/ 递归匹配

因此要想递归匹配目录下的所有文件就需要 **/*,而 rglob 是一个快捷方法可以只指定 *

IO 接口

Path 上直接绑定了 open 等方法来读取文件的内容:

  • Path.open(mode='r'): 打开路径指向的文件,等价于 open(Path),同样支持上下文管理
  • Path.read_text(encoding=None): 以字符串形式返回路径指向的文件
  • Path.read_bytes(): 以字节对象形式返回路径指向的文件
  • Path.write_text(data, encoding=None, errors=None, newline=None): 将文件以文件模式打开('w'),写入 data 后关闭
  • Path.write_bytes(data): 将文件以二进制模式打开('wb'),写入 data 后关闭