Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 428 – pathlib 模块 – 面向对象的 文件系统路径

作者:
Antoine Pitrou <solipsis at pitrou.net>
状态:
最终
类型:
标准跟踪
创建:
2012-07-30
Python 版本:
3.4
历史记录:
2012-10-05
决议:
Python-Dev 消息

目录

摘要

此 PEP 提出将第三方模块 pathlib 纳入标准库。 按照 PEP 411 中的描述,纳入是在临时标签下进行的。 因此,可以对 API 进行更改,无论是作为 PEP 过程的一部分,还是在标准库中接受后(并在移除临时标签之前)。

该库的目的是为文件系统路径提供一个简单的类层次结构,以及用户对这些路径进行常见操作。

实现

此提案的实现是在 pathlib 的 Mercurial 代码库pep428 分支中进行跟踪的。

为什么是面向对象的 API

使用专用类来表示文件系统路径的理由与其他类型的无状态对象(如日期、时间或 IP 地址)相同。 Python 已经慢慢地从严格地复制 C 语言的 API 转变为为各种常用功能提供更好、更实用的抽象。 即使此 PEP 未被接受, 也可能有一天,标准库将采用另一种形式的文件系统处理抽象。

事实上,很多人更喜欢使用 datetime 模块提供的 高级对象来处理日期和时间,而不是使用数字时间戳和 time 模块 API。 此外,使用专用类可以默认启用理想的行为,例如 Windows 路径的区分大小写。

提案

类层次结构

pathlib 模块实现了一个简单的类层次结构

                +----------+
                |          |
       ---------| PurePath |--------
       |        |          |       |
       |        +----------+       |
       |             |             |
       |             |             |
       v             |             v
+---------------+    |    +-----------------+
|               |    |    |                 |
| PurePosixPath |    |    | PureWindowsPath |
|               |    |    |                 |
+---------------+    |    +-----------------+
       |             v             |
       |          +------+         |
       |          |      |         |
       |   -------| Path |------   |
       |   |      |      |     |   |
       |   |      +------+     |   |
       |   |                   |   |
       |   |                   |   |
       v   v                   v   v
  +-----------+           +-------------+
  |           |           |             |
  | PosixPath |           | WindowsPath |
  |           |           |             |
  +-----------+           +-------------+

该层次结构将路径类划分为两个维度

  • 一个路径类可以是纯的或具体的: 纯类仅支持不需要执行任何实际 I/O 的操作,这些操作是大多数路径操作; 具体类支持所有纯类的操作,以及执行 I/O 的操作。
  • 根据表示的路径类型的文件系统语义,路径类属于特定的风格。 pathlib 实现了两种风格: Windows 路径用于在 Windows 系统中体现的文件系统语义,POSIX 路径用于其他系统。

任何纯类都可以在任何系统上实例化:例如,你可以 在 Windows 下操作 PurePosixPath 对象, 在 Unix 下操作 PureWindowsPath 对象,等等。 但是,具体类只能在匹配的系统上实例化:事实上,用 WindowsPath 对象在 Unix 下执行 I/O,或反之,将会导致错误。

此外,还有两个基类也充当系统相关的工厂: PurePath 将根据操作系统实例化 PurePosixPathPureWindowsPath。 类似地, Path 将实例化 PosixPathWindowsPath

预计在大多数情况下,使用 Path 类就足够了,这就是它具有所有类中最短名称的原因。

与内置函数不冲突

在此提案中,路径类不派生自任何内置类型。 这与一些其他 Path 类提案形成对比,这些提案是从 str 派生的。 它们也不假装实现序列协议: 如果你想让路径充当序列, 你必须查找专用属性(parts 属性)。

不从 str 继承背后的关键原因是防止意外地对表示路径的字符串和不表示路径的字符串执行操作,例如 path + an_accident。 由于对字符串的操作不一定能导致有效的或预期的文件系统路径, “显式优于隐式” 通过不进行子类化,避免了对字符串的意外操作。 Python 核心开发人员的一篇 博客文章 详细介绍了此特定设计决策背后的原因。

不可变性

Path 对象是不可变的,这使它们可哈希,并且还能防止一类编程错误。

合理的行为

很少重用来自 os.path 的功能。 许多 os.path 函数由于向后兼容性而与令人困惑或完全错误的行为相关联(例如, os.path.abspath() 在 首次解析符号链接之前会简化 “..” 路径组件)。

比较

相同风格的路径是可以比较和排序的,无论是纯路径还是具体路径。

>>> PurePosixPath('a') == PurePosixPath('b')
False
>>> PurePosixPath('a') < PurePosixPath('b')
True
>>> PurePosixPath('a') == PosixPath('a')
True

比较和排序 Windows 路径对象是不区分大小写的。

>>> PureWindowsPath('a') == PureWindowsPath('A')
True

不同风格的路径总是比较不相等, 并且不能进行排序。

>>> PurePosixPath('a') == PureWindowsPath('a')
False
>>> PurePosixPath('a') < PureWindowsPath('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: PurePosixPath() < PureWindowsPath()

路径与内置类型(如 str) 和任何其他类型的实例比较不相等, 并且不能与其进行排序。

有用的符号

该 API 尝试提供有用的符号, 同时避免使用魔法。 以下是一些示例

>>> p = Path('/home/antoine/pathlib/setup.py')
>>> p.name
'setup.py'
>>> p.suffix
'.py'
>>> p.root
'/'
>>> p.parts
('/', 'home', 'antoine', 'pathlib', 'setup.py')
>>> p.relative_to('/home/antoine')
PosixPath('pathlib/setup.py')
>>> p.exists()
True

纯路径 API

PurePath API 的理念是提供一套一致且有用的路径操作,而不是像 os.path 那样暴露一堆函数。

定义

首先是一些约定

  • 所有路径都可以有驱动器和根目录。 对于 POSIX 路径,驱动器始终为空。
  • 相对路径既没有驱动器,也没有根目录。
  • 如果 POSIX 路径具有根目录,则它是绝对路径。 如果 Windows 路径同时具有驱动器和根目录,则它是绝对路径。 Windows UNC 路径(例如 \\host\share\myfile.txt) 始终具有驱动器和根目录(此处分别为 \\host\share\)。
  • 具有驱动器或根目录的路径称为锚定路径。 它的锚点是驱动器和根目录的连接。 在 POSIX 下, “锚定” 与 “绝对” 相同。

构造

我们将介绍构造和连接,因为它们暴露了相似的语义。

构造路径最简单的方法是传递它的字符串表示形式

>>> PurePath('setup.py')
PurePosixPath('setup.py')

会消除多余的路径分隔符和 "." 组件

>>> PurePath('a///b/c/./d/')
PurePosixPath('a/b/c/d')

如果你传递多个参数,它们会自动连接起来

>>> PurePath('docs', 'Makefile')
PurePosixPath('docs/Makefile')

连接语义类似于 os.path.join, 锚定路径会忽略先前连接的组件的信息

>>> PurePath('/etc', '/usr', 'bin')
PurePosixPath('/usr/bin')

但是,对于 Windows 路径,驱动器会根据需要保留

>>> PureWindowsPath('c:/foo', '/Windows')
PureWindowsPath('c:/Windows')
>>> PureWindowsPath('c:/foo', 'd:')
PureWindowsPath('d:')

此外,路径分隔符会规范化为平台默认值

>>> PureWindowsPath('a/b') == PureWindowsPath('a\\b')
True

会消除多余的路径分隔符和 "." 组件, 但不会消除 ".." 组件

>>> PurePosixPath('a//b/./c/')
PurePosixPath('a/b/c')
>>> PurePosixPath('a/../b')
PurePosixPath('a/../b')

多个前导斜杠的处理方式根据路径风格的不同而不同。 它们始终保留在 Windows 路径中(因为 UNC 符号)

>>> PureWindowsPath('//some/path')
PureWindowsPath('//some/path/')

在 POSIX 中,它们会折叠,除非有两个前导斜杠, 这是 POSIX 规范中有关 路径名解析 的一个特殊情况(这也 是 Cygwin 兼容性所必需的)

>>> PurePosixPath('///some/path')
PurePosixPath('/some/path')
>>> PurePosixPath('//some/path')
PurePosixPath('//some/path')

在不传入任何参数的情况下调用构造函数会创建一个指向逻辑 “当前目录” 的路径对象( 不查找其绝对路径,这是具体路径上的 cwd() 类方法的工作)。

>>> PurePosixPath()
PurePosixPath('.')

表示

要表示路径(例如传递给第三方库), 只需对其调用 str()

>>> p = PurePath('/home/antoine/pathlib/setup.py')
>>> str(p)
'/home/antoine/pathlib/setup.py'
>>> p = PureWindowsPath('c:/windows')
>>> str(p)
'c:\\windows'

要强制使用正斜杠的字符串表示形式, 请使用 as_posix() 方法。

>>> p.as_posix()
'c:/windows'

要获取字节表示形式(这在 Unix 系统下可能有用), 请对其调用 bytes(), 它在内部使用 os.fsencode()

>>> bytes(p)
b'/home/antoine/pathlib/setup.py'

要以 file: URI 的形式表示路径, 请调用 as_uri() 方法。

>>> p = PurePosixPath('/etc/passwd')
>>> p.as_uri()
'file:///etc/passwd'
>>> p = PureWindowsPath('c:/Windows')
>>> p.as_uri()
'file:///c:/Windows'

路径的 repr() 始终使用正斜杠, 即使在 Windows 下也是如此, 以便于阅读, 并提醒用户正斜杠是可以接受的。

>>> p = PureWindowsPath('c:/Windows')
>>> p
PureWindowsPath('c:/Windows')

属性

每个路径都提供了一些简单的属性(每个属性都可以为空)。

>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.drive
'c:'
>>> p.root
'\\'
>>> p.anchor
'c:\\'
>>> p.name
'pathlib.tar.gz'
>>> p.stem
'pathlib.tar'
>>> p.suffix
'.gz'
>>> p.suffixes
['.tar', '.gz']

派生新的路径

连接

可以使用 / 运算符将路径与另一个路径连接。

>>> p = PurePosixPath('foo')
>>> p / 'bar'
PurePosixPath('foo/bar')
>>> p / PurePosixPath('bar')
PurePosixPath('foo/bar')
>>> 'bar' / p
PurePosixPath('bar/foo')

与构造函数类似,可以指定多个路径组件,无论是合并的还是单独的。

>>> p / 'bar/xyzzy'
PurePosixPath('foo/bar/xyzzy')
>>> p / 'bar' / 'xyzzy'
PurePosixPath('foo/bar/xyzzy')

还提供了一个 `joinpath()` 方法,具有相同的行为。

>>> p.joinpath('Python')
PurePosixPath('foo/Python')

更改路径的最后一个组件

`with_name()` 方法返回一个新的路径,名称已更改。

>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.with_name('setup.py')
PureWindowsPath('c:/Downloads/setup.py')

如果路径没有实际名称,则会引发 `ValueError` 错误。

>>> p = PureWindowsPath('c:/')
>>> p.with_name('setup.py')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pathlib.py", line 875, in with_name
    raise ValueError("%r has an empty name" % (self,))
ValueError: PureWindowsPath('c:/') has an empty name
>>> p.name
''

`with_suffix()` 方法返回一个新的路径,后缀已更改。但是,如果路径没有后缀,则会添加新的后缀。

>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.with_suffix('.bz2')
PureWindowsPath('c:/Downloads/pathlib.tar.bz2')
>>> p = PureWindowsPath('README')
>>> p.with_suffix('.bz2')
PureWindowsPath('README.bz2')

使路径相对

`relative_to()` 方法计算路径相对于另一个路径的相对差。

>>> PurePosixPath('/usr/bin/python').relative_to('/usr')
PurePosixPath('bin/python')

如果方法无法返回有意义的值,则会引发 ValueError 错误。

>>> PurePosixPath('/usr/bin/python').relative_to('/etc')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pathlib.py", line 926, in relative_to
    .format(str(self), str(formatted)))
ValueError: '/usr/bin/python' does not start with '/etc'

类似序列的访问

`parts` 属性返回一个元组,提供对路径组件的只读序列访问。

>>> p = PurePosixPath('/etc/init.d')
>>> p.parts
('/', 'etc', 'init.d')

Windows 路径将驱动器和根目录处理为单个路径组件。

>>> p = PureWindowsPath('c:/setup.py')
>>> p.parts
('c:\\', 'setup.py')

(将它们分开将是错误的,因为 `C:` 不是 `C:\\` 的父目录)。

`parent` 属性返回路径的逻辑父目录。

>>> p = PureWindowsPath('c:/python33/bin/python.exe')
>>> p.parent
PureWindowsPath('c:/python33/bin')

`parents` 属性返回路径的逻辑祖先的不可变序列。

>>> p = PureWindowsPath('c:/python33/bin/python.exe')
>>> len(p.parents)
3
>>> p.parents[0]
PureWindowsPath('c:/python33/bin')
>>> p.parents[1]
PureWindowsPath('c:/python33')
>>> p.parents[2]
PureWindowsPath('c:/')

查询

`is_relative()` 如果路径是相对路径(请参阅上面的定义),则返回 True,否则返回 False。

`is_reserved()` 如果 Windows 路径是保留路径,例如 `CON` 或 `NUL`,则返回 True。对于 POSIX 路径,它始终返回 False。

`match()` 将路径与通配符模式进行匹配。它对各个部分进行操作,并从右边进行匹配。

>>> p = PurePosixPath('/usr/bin')
>>> p.match('/usr/b*')
True
>>> p.match('usr/b*')
True
>>> p.match('b*')
True
>>> p.match('/u*')
False

此行为符合以下期望。

  • 像“*.py”这样的简单模式只要最后一个部分匹配,就可以匹配任意长的路径,例如“/usr/foo/bar.py”。
  • 更长的模式也可以用于更复杂的匹配,例如“/usr/foo/*.py”匹配“/usr/foo/bar.py”。

具体路径 API

除了纯 API 的操作外,具体路径还提供其他方法,这些方法实际上访问文件系统以查询或更改信息。

构造

类方法 `cwd()` 创建一个指向当前工作目录的绝对路径对象。

>>> Path.cwd()
PosixPath('/home/antoine/pathlib')

文件元数据

`stat()` 返回文件的 stat() 结果;类似地,`lstat()` 返回文件的 lstat() 结果(如果文件是符号链接,则不同)。

>>> p.stat()
posix.stat_result(st_mode=33277, st_ino=7483155, st_dev=2053, st_nlink=1, st_uid=500, st_gid=500, st_size=928, st_atime=1343597970, st_mtime=1328287308, st_ctime=1343597964)

更高级别的方法有助于检查文件的类型。

>>> p.exists()
True
>>> p.is_file()
True
>>> p.is_dir()
False
>>> p.is_symlink()
False
>>> p.is_socket()
False
>>> p.is_fifo()
False
>>> p.is_block_device()
False
>>> p.is_char_device()
False

文件所有者和组名称(而不是数字 ID)通过相应的方法进行查询。

>>> p = Path('/etc/shadow')
>>> p.owner()
'root'
>>> p.group()
'shadow'

路径解析

`resolve()` 方法使路径变为绝对路径,并在此过程中解析任何符号链接(类似于 POSIX realpath() 调用)。它是唯一能够删除“`..`”路径组件的操作。在 Windows 上,此方法还会注意返回规范路径(带正确的区分大小写)。

目录遍历

通过调用 `iterdir()` 方法可以进行简单的(非递归)目录访问,该方法返回子路径的迭代器。

>>> p = Path('docs')
>>> for child in p.iterdir(): child
...
PosixPath('docs/conf.py')
PosixPath('docs/_templates')
PosixPath('docs/make.bat')
PosixPath('docs/index.rst')
PosixPath('docs/_build')
PosixPath('docs/_static')
PosixPath('docs/Makefile')

这允许通过列表推导进行简单的过滤。

>>> p = Path('.')
>>> [child for child in p.iterdir() if child.is_dir()]
[PosixPath('.hg'), PosixPath('docs'), PosixPath('dist'), PosixPath('__pycache__'), PosixPath('build')]

还提供简单和递归的通配符匹配。

>>> for child in p.glob('**/*.py'): child
...
PosixPath('test_pathlib.py')
PosixPath('setup.py')
PosixPath('pathlib.py')
PosixPath('docs/conf.py')
PosixPath('build/lib/pathlib.py')

文件打开

`open()` 方法提供类似于内置 `open()` 方法的文件打开 API。

>>> p = Path('setup.py')
>>> with p.open() as f: f.readline()
...
'#!/usr/bin/env python3\n'

文件系统修改

提供了一些常用的文件系统操作作为方法:`touch()`、`mkdir()`、`rename()`、`replace()`、`unlink()`、`rmdir()`、`chmod()`、`lchmod()`、`symlink_to()`。可以提供更多操作,例如 shutil 模块的一些功能。

可以在 pathlib 文档 中找到所提议 API 的详细文档。

讨论

除法运算符

除法运算符在关于路径连接运算符的 投票 中首先出现。最初版本的 pathlib 使用方括号(即 `__getitem__`)代替。

joinpath()

`joinpath()` 方法最初称为 `join()`,但一些人反对它可能与语义不同的 `str.join()` 混淆。因此,它被重命名为 `joinpath()`。

区分大小写

Windows 用户认为文件系统路径不区分大小写,并希望路径对象遵守该特性,即使在某些罕见情况下,某些外国文件系统安装可能在 Windows 下区分大小写。

用一位评论者的话说,

“如果 `glob(”*.py”)` 在 Windows 上无法找到 `SETUP.PY`,那将是一场可用性灾难”。

——Paul Moore 在 https://mail.python.org/pipermail/python-dev/2013-April/125254.html 中。


来源:https://github.com/python/peps/blob/main/peps/pep-0428.rst

最后修改时间:2023-09-09 17:39:29 GMT