7.3. pathlib — 文件路径对象

未匹配的标注

目的:使用高级的面向对象 API 解析,构建,检测文件名和路径而不是低级的字符串操作。

路径表示

pathlib 包含了大量用于管理 POSIX 标准或者 Windows 语法的文件系统路径的类。它包含所谓的「纯」类,仅仅用于去操作字符串而不与实际的文件系统交互;也包含「具体」类,它们扩展了 API 以包含反映或修改本地文件系统数据的操作。

PurePosixPathPureWindowsPath 可以实例化于任何的操作系统,仅仅用做处理路径名称。如果要实例化一个与真实文件系统相关的具体类,应该使用 Path, 它会根据当前的操作系统类型得到 PosixPath 或者 WindowsPath 的实例。

构建路径

要实例化一个新路径,将一个字符串作为类的第一个参数,这个字符串是该路径对象的字符串形式。要创建一个相对于现有路径的新路径,可是使用 / 运算符进行扩展,该运算符的参数可以是一个字符串或者一个路径对象。

pathlib_operator.py

import pathlib

usr = pathlib.PurePosixPath('/usr')
print(usr)

usr_local = usr / 'local'
print(usr_local)

usr_share = usr / pathlib.PurePosixPath('share')
print(usr_share)

root = usr / '..'
print(root)

etc = root / '/etc/'
print(etc)

正如 root 展示的那样,/ 运算符仅仅是将给定的参数组合起来而已,当结果中包含父目录引用 「..」时,并没有去解析。而且,如果一个路径片段是以路径分隔符 / 开始,它将被作为一个新的根路径,就像 os.path.join()。额外的路径分隔符会从路径的中间移除,如同 etc 示例。

$ python3 pathlib_operator.py

/usr
/usr/local
/usr/share
/usr/..
/etc

具体类包含一个 resolve() 方法,它会通过查找文件系统和符号链接去生成该名称引用的规范化的绝对路径。

pathlib_resolve.py

import pathlib

usr_local = pathlib.Path('/usr/local')
share = usr_local / '..' / 'share'
print(share.resolve())

这里的相对路径被解析成绝对路径 /usr/share。如果输入路径中包含符号链接,那么也会扩展这些符号链接以允许解析路径直接引用目标。

$ python3 pathlib_resolve.py

/usr/share

如果要在事先不知道路径片段时构建路径,可以使用 joinpath() ,只需要将每个路径片段作为一个独立的参数传递给它。

pathlib_joinpath.py

import pathlib

root = pathlib.PurePosixPath('/')
subdirs = ['usr', 'local']
usr_local = root.joinpath(*subdirs)
print(usr_local)

如同 / 运算符,joinpath() 创建了一个新的实例。

$ python3 pathlib_joinpath.py

/usr/local

给定一个存在的路径对象,可以新建一个有细微差别的新路径,例如引用相同目录下的不同文件。可以给 with_name() 方法传递一个不同的文件名称创建一个新的路径,相比原路径只是替换掉文件部分。使用 with_suffix() 方法也可以创建一个新的路径,只是用新一个新的扩展名替换掉原来的。

pathlib_from_existing.py

import pathlib

ind = pathlib.PurePosixPath('source/pathlib/index.rst')
print(ind)

py = ind.with_name('pathlib_from_existing.py')
print(py)

pyc = py.with_suffix('.pyc')
print(pyc)

这两个方法都返回新的对象,而原来的保持不变。

$ python3 pathlib_from_existing.py

source/pathlib/index.rst
source/pathlib/pathlib_from_existing.py
source/pathlib/pathlib_from_existing.pyc

解析路径

Path 对象具有从名称中提取部分值的方法和属性。例如,parts 属性会生成一个基于路径分隔符解析的路径片段序列。

pathlib_parts.py

import pathlib

p = pathlib.PurePosixPath('/usr/local')
print(p.parts)

结果序列是一个元组,反映出路径实例的不变性。

$ python3 pathlib_parts.py

('/', 'usr', 'local')

这儿有两种方式从给定的路径对象中「向上」导航文件系统层次结构。parent 属性引用一个包含当前路径的目录的新实例,如同 os.path.dirname() 的返回值。parents 属性生成一个父目录可迭代对象,从当前目录「向上」直到根目录。

pathlib_parents.py

import pathlib

p = pathlib.PurePosixPath('/usr/local/lib')

print('parent: {}'.format(p.parent))

print('\nhierarchy:')
for up in p.parents:
    print(up)

例子中遍历 parents 属性并且打印成员。

$ python3 pathlib_parents.py

parent: /usr/local

hierarchy:
/usr/local
/usr
/

路径的其他部分可以通过路径对象的属性进行访问。name 属性包含路径最后一部分的值,即最后一个路径分隔符后面的值(等同于 os.path.basename() )。suffix 代表扩展分隔符后面的值,stem 属性代表后缀之前的部分。

pathlib_name.py

import pathlib

p = pathlib.PurePosixPath('./source/pathlib/pathlib_name.py')
print('path  : {}'.format(p))
print('name  : {}'.format(p.name))
print('suffix: {}'.format(p.suffix))
print('stem  : {}'.format(p.stem))

尽管 suffixstem 的值看起来有点像 os.path.splittext() 的结果,但是他仅仅基于 name 属性的值而不是完整的路径。

$ python3 pathlib_name.py

path  : source/pathlib/pathlib_name.py
name  : pathlib_name.py
suffix: .py
stem  : pathlib_name

创建具体的路径

可以通过表示系统上文件,目录或者符号链接的字符串实例化具体类 Path。该类还提供了一些快捷的方法用来创建常用对象,例如当前目录或者用户主目录。

pathlib_convenience.py

import pathlib

home = pathlib.Path.home()
print('home: ', home)

cwd = pathlib.Path.cwd()
print('cwd : ', cwd)

这两个方法创建的 Path 实例都预先设置了系统的绝对路径。

$ python3 pathlib_convenience.py

home:  /Users/dhellmann
cwd :  /Users/dhellmann/PyMOTW

目录内容

这有三个访问目录列表的方法,以发现文件系统上可用的文件名称。iterdir() 是一个生成器,生成目录列表中每一个文件的 Path 实例。

pathlib_iterdir.py

import pathlib

p = pathlib.Path('.')

for f in p.iterdir():
    print(f)

如果 Path 不是引用一个目录,那么 iterdir() 将会引发 NotADirectoryError

$ python3 pathlib_iterdir.py

example_link
index.rst
pathlib_chmod.py
pathlib_convenience.py
pathlib_from_existing.py
pathlib_glob.py
pathlib_iterdir.py
pathlib_joinpath.py
pathlib_mkdir.py
pathlib_name.py
pathlib_operator.py
pathlib_ownership.py
pathlib_parents.py
pathlib_parts.py
pathlib_read_write.py
pathlib_resolve.py
pathlib_rglob.py
pathlib_rmdir.py
pathlib_stat.py
pathlib_symlink_to.py
pathlib_touch.py
pathlib_types.py
pathlib_unlink.py

可以使用 glob() 去查找到那些匹配模式的文件。

pathlib_glob.py

import pathlib

p = pathlib.Path('..')

for f in p.glob('*.rst'):
    print(f)

这个脚本展示了列出父目录中所有 reStructuredText 文件。

$ python3 pathlib_glob.py

../about.rst
../algorithm_tools.rst
../book.rst
../compression.rst
../concurrency.rst
../cryptographic.rst
../data_structures.rst
../dates.rst
../dev_tools.rst
../email.rst
../file_access.rst
../frameworks.rst
../i18n.rst
../importing.rst
../index.rst
../internet_protocols.rst
../language.rst
../networking.rst
../numeric.rst
../persistence.rst
../porting_notes.rst
../runtime_services.rst
../text.rst
../third_party.rst
../unix.rst

glob 处理器支持使用模式前缀 ** 进行递归查找或者直接使用 rglob(),直白地说 rglob("*.py") 等同于 glob("**/*.py")

pathlib_rglob.py

import pathlib

p = pathlib.Path('..')

for f in p.rglob('pathlib_*.py'):
    print(f)

因为例子中是从当前脚本的父目录开始,因此需要进行递归搜索才能找到匹配 pathlib_*.py 的示例文件。

$ python3 pathlib_rglob.py

../pathlib/pathlib_chmod.py
../pathlib/pathlib_convenience.py
../pathlib/pathlib_from_existing.py
../pathlib/pathlib_glob.py
../pathlib/pathlib_iterdir.py
../pathlib/pathlib_joinpath.py
../pathlib/pathlib_mkdir.py
../pathlib/pathlib_name.py
../pathlib/pathlib_operator.py
../pathlib/pathlib_ownership.py
../pathlib/pathlib_parents.py
../pathlib/pathlib_parts.py
../pathlib/pathlib_read_write.py
../pathlib/pathlib_resolve.py
../pathlib/pathlib_rglob.py
../pathlib/pathlib_rmdir.py
../pathlib/pathlib_stat.py
../pathlib/pathlib_symlink_to.py
../pathlib/pathlib_touch.py
../pathlib/pathlib_types.py
../pathlib/pathlib_unlink.py

读写文件

每个 Path 实例都包含了处理它引用的文件内容的方法。可以使用 read_bytes() 或者 read_text() 来即时获取文件内容。使用 write_bytes() 或者 write_text() 来写文件。可以使用 open() 方法打开文件获得文件描述符,而不是将文件名传递给内建的 open() 方法。

pathlib_read_write.py

import pathlib

f = pathlib.Path('example.txt')

f.write_bytes('This is the content'.encode('utf-8'))

with f.open('r', encoding='utf-8') as handle:
    print('read from open(): {!r}'.format(handle.read()))

print('read_text(): {!r}'.format(f.read_text('utf-8')))

这些便捷的方法会在打开或者写入文件之前进行类型检查,否则他们就等同于直接进行操作。

$ python3 pathlib_read_write.py

read from open(): 'This is the content'
read_text(): 'This is the content'

操作目录和符号链接

表示不存在的目录或者符号链接的路径实例可以用于去创建与系统关联的实体。

pathlib_mkdir.py

import pathlib

p = pathlib.Path('example_dir')

print('Creating {}'.format(p))
p.mkdir()

如果路径确实已经存在, mkdir() 将会引发 FileExistsError 错误。

$ python3 pathlib_mkdir.py

Creating example_dir

$ python3 pathlib_mkdir.py

Creating example_dir
Traceback (most recent call last):
  File "pathlib_mkdir.py", line 16, in <module>
    p.mkdir()
  File ".../lib/python3.6/pathlib.py", line 1226, in mkdir
    self._accessor.mkdir(self, mode)
  File ".../lib/python3.6/pathlib.py", line 387, in wrapped
    return strfunc(str(pathobj), *args)
FileExistsError: [Errno 17] File exists: 'example_dir'

可以使用 symlink_to() 创建一个符号链接。链接名称将会被命名为 path 实例的值并且引用传递给 symlink_to() 的目标。

pathlib_symlink_to.py

import pathlib

p = pathlib.Path('example_link')

p.symlink_to('index.rst')

print(p)
print(p.resolve().name)

这个例子创建了一个符号链接,然后用 resolve() 方法读取符号链接指向的目标的名称。

$ python3 pathlib_symlink_to.py

example_link
index.rst

文件类型

一个 Path 实例包含了几个用于去检测引用的文件类型的方法。这个例子创建了几个不同类型的文件并检测这些文件以及本地系统上可用的其他特定设备文件。

pathlib_types.py

import itertools
import os
import pathlib

root = pathlib.Path('test_files')

# 清除之前运行的结果
if root.exists():
    for f in root.iterdir():
        f.unlink()
else:
    root.mkdir()

# 创建测试文件
(root / 'file').write_text(
    'This is a regular file', encoding='utf-8')
(root / 'symlink').symlink_to('file')
os.mkfifo(str(root / 'fifo'))

# 检查文件类型
to_scan = itertools.chain(
    root.iterdir(),
    [pathlib.Path('/dev/disk0'),
     pathlib.Path('/dev/console')],
)
hfmt = '{:18s}' + ('  {:>5}' * 6)
print(hfmt.format('Name', 'File', 'Dir', 'Link', 'FIFO', 'Block',
                  'Character'))
print()

fmt = '{:20s}  ' + ('{!r:>5}  ' * 6)
for f in to_scan:
    print(fmt.format(
        str(f),
        f.is_file(),
        f.is_dir(),
        f.is_symlink(),
        f.is_fifo(),
        f.is_block_device(),
        f.is_char_device(),
    ))

is_dir(), is_file(), is_symlink(), is_socket(), is_fifo(), is_block_device() 以及 is_char_device() 都不需要参数。

$ python3 pathlib_types.py

Name                 File    Dir   Link   FIFO  Block  Character

test_files/fifo       False  False  False   True  False  False
test_files/file        True  False  False  False  False  False
test_files/symlink     True  False   True  False  False  False
/dev/disk0            False  False  False  False   True  False
/dev/console          False  False  False  False  False   True

文件属性

文件的详细信息可以通过 stat() 或者 lstat() (检查符号链接状态)查看。这两个方法的返回值同 os.stat()os.lstat() 一样。

pathlib_stat.py

import pathlib
import sys
import time

if len(sys.argv) == 1:
    filename = __file__
else:
    filename = sys.argv[1]

p = pathlib.Path(filename)
stat_info = p.stat()

print('{}:'.format(filename))
print('  Size:', stat_info.st_size)
print('  Permissions:', oct(stat_info.st_mode))
print('  Owner:', stat_info.st_uid)
print('  Device:', stat_info.st_dev)
print('  Created      :', time.ctime(stat_info.st_ctime))
print('  Last modified:', time.ctime(stat_info.st_mtime))
print('  Last accessed:', time.ctime(stat_info.st_atime))

以上示例的输出结果依赖于代码如何安装。可以通过给 pathlib_stat.py 传入不同的文件而观察输出结果。

$ python3 pathlib_stat.py

pathlib_stat.py:
  Size: 607
  Permissions: 0o100644
  Owner: 527
  Device: 16777220
  Created      : Thu Dec 29 12:38:23 2016
  Last modified: Thu Dec 29 12:38:23 2016
  Last accessed: Sun Mar 18 16:21:41 2018

$ python3 pathlib_stat.py index.rst

index.rst:
  Size: 19569
  Permissions: 0o100644
  Owner: 527
  Device: 16777220
  Created      : Sun Mar 18 16:11:31 2018
  Last modified: Sun Mar 18 16:11:31 2018
  Last accessed: Sun Mar 18 16:21:40 2018

要更简单地访问文件的用户信息,可以使用 owner()group()

pathlib_ownership.py

import pathlib

p = pathlib.Path(__file__)

print('{} is owned by {}/{}'.format(p, p.owner(), p.group()))

stat() 方法只是返回了数字类型的系统 ID ,但是 owner()group() 会查找与 ID 相关的名称。

$ python3 pathlib_ownership.py

pathlib_ownership.py is owned by dhellmann/dhellmann

touch() 方法于 Unix 命令 touch 类似,可以用于创建文件或者更新现有文件的修改时间和权限。

pathlib_touch.py

import pathlib
import time

p = pathlib.Path('touched')
if p.exists():
    print('already exists')
else:
    print('creating new')

p.touch()
start = p.stat()

time.sleep(1)

p.touch()
end = p.stat()

print('Start:', time.ctime(start.st_mtime))
print('End  :', time.ctime(end.st_mtime))

多次运行示例后将会在后续运行中更新文件。

$ python3 pathlib_touch.py

creating new
Start: Sun Mar 18 16:21:41 2018
End  : Sun Mar 18 16:21:42 2018

$ python3 pathlib_touch.py

already exists
Start: Sun Mar 18 16:21:42 2018
End  : Sun Mar 18 16:21:43 2018

权限

在类 Unix 系统上,文件权限模式可以作为整数传递给 chmod() 方法进行更改。权限模式值可以通过 stat 模块中的常量进行构造。本示例切换了用户的执行权限。

pathlib_chmod.py

import os
import pathlib
import stat

# 创建一个全新的测试文件
f = pathlib.Path('pathlib_chmod_example.txt')
if f.exists():
    f.unlink()
f.write_text('contents')

# 使用 stat 方法来检测文件权限
existing_permissions = stat.S_IMODE(f.stat().st_mode)
print('Before: {:o}'.format(existing_permissions))

# 决定用啥方法来处理
if not (existing_permissions & os.X_OK):
    print('Adding execute permission')
    new_permissions = existing_permissions | stat.S_IXUSR
else:
    print('Removing execute permission')
    # 使用 xor 来移除用户可执行权限
    new_permissions = existing_permissions ^ stat.S_IXUSR

# 修改权限,并打印修改后的权限结果
f.chmod(new_permissions)
after_permissions = stat.S_IMODE(f.stat().st_mode)
print('After: {:o}'.format(after_permissions))

假设脚本运行时它有权限更改文件模式。

$ python3 pathlib_chmod.py

Before: 644
Adding execute permission
After: 744

删除

这有两个方法用于移除文件系统上的文件或者目录。移除一个空目录可以使用 rmdir()

pathlib_rmdir.py

import pathlib

p = pathlib.Path('example_dir')

print('Removing {}'.format(p))
p.rmdir()

当移除的目录不存在的时候,就会引发 FileNotFoundError 异常。尝试删除一个非空目录也会引起错误。

$ python3 pathlib_rmdir.py

Removing example_dir

$ python3 pathlib_rmdir.py

Removing example_dir
Traceback (most recent call last):
  File "pathlib_rmdir.py", line 16, in <module>
    p.rmdir()
  File ".../lib/python3.6/pathlib.py", line 1270, in rmdir
    self._accessor.rmdir(self)
  File ".../lib/python3.6/pathlib.py", line 387, in wrapped
    return strfunc(str(pathobj), *args)
FileNotFoundError: [Errno 2] No such file or directory:
'example_dir'

对于文件,系统链接以及其他大多数的路径类型应该使用 unlink()

pathlib_unlink.py

import pathlib

p = pathlib.Path('touched')

p.touch()

print('exists before removing:', p.exists())

p.unlink()

print('exists after removing:', p.exists())

用户必须要有权限去移除文件,符号链接,socket 或者其他文件系统对象。

$ python3 pathlib_unlink.py

exists before removing: True
exists after removing: False

推荐阅读

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~