pathlib:文件系统路径作为对象

pathlib:文件系统路径作为对象

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

路径表示

  • pathlib包含用于管理使用POSIX标准或Microsoft Windows语法格式化的文件系统路径的类。它包括所谓的“pure”类,它们对字符串进行操作,但不与实际的文件系统交互;和“concrete”类,它们将API扩展到包含反映或修改本地文件系统数据的操作。
  • pure类PurePosixPath和PureWindowsPath可以在任何操作系统上实例化和使用,因为他们只在名字上工作。为了实例化correct类来处理真正的文件系统,使用Path会根据平台的不同自动来获取PosixPath或WindowsPath。

构建Paths

要实例化一个新的路径,需要给一个字符串作为第一个参数。路径对象的字符串表示形式是该名称值。要创建一个引用相对于现有路径的值的新路径,使用/运算符来扩展路径。运算符的参数可以是字符串或其他路径对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 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值所示,操作符将所给的路径值接合在一起,并且在包含父目录引用“..”时不会规范化结果。但是,如果一个段以路径分隔符开始,它将被解释为一个新的“root”引用,就像os.path.join()。从路径值中间删除额外路径分隔符,像例子中的etc一样。

1
2
3
4
5
6
7
$ python3 pathlib_operator.py
/usr
/usr/local
/usr/share
/usr/..
/etc

concrete路径类包含了一个resolve()方法,用于通过查看目录和符号链接的文件系统并生成名称引用的绝对路径来标准化路径。

1
2
3
4
5
6
7
# pathlib_resolve.py
import pathlib
usr_local = pathlib.Path('/usr/local')
share = usr_local / '..' / 'share'
print(share.resolve())

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

1
2
3
$ python3 pathlib_resolve.py
/usr/share

要在事先不知道段的情况下构建路径,使用joinpath(),将每个路径段作为单独的参数传递。

1
2
3
4
5
6
7
8
# pathlib_joinpath.py
import pathlib
root = pathlib.PurePosixPath('/')
subdirs = ['usr', 'local']
usr_local = root.joinpath(*subdirs)
print(usr_local)

和使用/操作符一样,调用joinpath()创建一个新的实例。

1
2
3
$ python3 pathlib_joinpath.py
/usr/local

给定一个现有的路径对象,很容易建立一个小的差异新对象,如引用同一目录中的不同文件。使用with_name()创建一个新路径,用不同的文件名替换路径的名称部分。使用with_suffix()创建一个新路径,用不同的值替换文件名的扩展名。

1
2
3
4
5
6
7
8
9
10
11
12
# 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)

这两种方法都返回新的对象,并保持左边部分不变。

1
2
3
4
5
$ python3 pathlib_from_existing.py
source/pathlib/index.rst
source/pathlib/pathlib_from_existing.py
source/pathlib/pathlib_from_existing.pyc

解析Paths

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

1
2
3
4
5
6
# pathlib_parts.py
import pathlib
p = pathlib.PurePosixPath('/usr/local')
print(p.parts)

该序列是一个元组,反映了路径实例的不变性。

1
2
3
$ python3 pathlib_parts.py
('/', 'usr', 'local')

有两种方法可以从一个给定的路径对象中“up”导航文件系统层次结构。parent属性引用了包含路径目录的新路径实例,这些值由os.path.dirname()返回。parents属性是一个迭代器,它产生父目录引用,不断地“up”路径层次直到到达根目录。

1
2
3
4
5
6
7
8
9
10
11
# 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项属性并打印成员值。

1
2
3
4
5
6
7
8
$ python3 pathlib_parents.py
parent: /usr/local
hierarchy:
/usr/local
/usr
/

路径的其他部分可以通过路径对象的属性来访问。name属性保存了最后一个路径分隔符后的路径的最后部分(与os.path.basename()产生的值相同)。suffix属性保存扩展分隔符后面的值,并且stem属性保留后缀之前的名称部分。

1
2
3
4
5
6
7
8
9
# 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)

尽管suffix和stem值和os.path.splitext()产生的值相似,但是这些值仅仅基于name属性,而不是完整路径。

1
2
3
4
5
6
$ python3 pathlib_name.py
path : source/pathlib/pathlib_name.py
name : pathlib_name.py
suffix: .py
stem : pathlib_name

创建Concrete Paths

concrete path类的实例可以通过引用文件系统上的文件,目录或符号链接的名称(或潜在名称)的字符串参数来创建。该类还提供了几种便捷方法来构建使用常用位置的目录,如当前工作目录和用户主目录。

1
2
3
4
5
6
7
8
9
# pathlib_convenience.py
import pathlib
home = pathlib.Path.home()
print('home: ', home)
cwd = pathlib.Path.cwd()
print('cwd : ', cwd)

这两种方法创建Path实例都是通过预填充绝对文件系统引用。

1
2
3
4
$ python3 pathlib_convenience.py
home: /Users/dhellmann
cwd : /Users/dhellmann/PyMOTW

目录内容

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

1
2
3
4
5
6
7
8
# pathlib_iterdir.py
import pathlib
p = pathlib.Path('.')
for f in p.iterdir():
print(f)

如果路径没有引用目录,则iterdir()会引发NotADirectoryError异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ 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()仅查找匹配模式的文件。

1
2
3
4
5
6
7
8
# pathlib_glob.py
import pathlib
p = pathlib.Path('..')
for f in p.glob('*.rst'):
print(f)

此示例显示脚本的父目录中的所有reStructuredText输入文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ 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()而不是glob()来进行递归扫描。

1
2
3
4
5
6
7
8
# pathlib_rglob.py
import pathlib
p = pathlib.Path('..')
for f in p.rglob('pathlib_*.py'):
print(f)

因为这个例子从父目录开始,所以需要递归搜索来找到匹配pathlib _ *.py的示例文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ 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()函数。

1
2
3
4
5
6
7
8
9
10
11
12
# 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')))

便捷方法在打开文件并写入之前进行一些类型检查,否则就相当于直接进行操作。

1
2
3
4
$ python3 pathlib_read_write.py
read from open(): 'This is the content'
read_text(): 'This is the content'

操纵目录和符号链接

表示不存在的目录或符号链接的路径可用于创建关联的文件系统条目。

1
2
3
4
5
6
7
8
# pathlib_mkdir.py
import pathlib
p = pathlib.Path('example_dir')
print('Creating {}'.format(p))
p.mkdir()

如果路径已经存在,mkdir()会引发一个FileExistsError异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ 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.5/pathlib.py", line 1214, in mkdir
self._accessor.mkdir(self, mode)
File ".../lib/python3.5/pathlib.py", line 371, in wrapped
return strfunc(str(pathobj), *args)
FileExistsError: [Errno 17] File exists: 'example_dir'

使用symlink_to()创建一个符号链接。该链接将根据路径的值进行命名,并将引用作为symlink_to()的参数的名称。

1
2
3
4
5
6
7
8
9
10
# pathlib_symlink_to.py
import pathlib
p = pathlib.Path('example_link')
p.symlink_to('index.rst')
print(p)
print(p.resolve().name)

这个例子创建了一个符号链接,然后使用resolve()来读取链接,找到它指向的内容并打印名称。

1
2
3
4
$ python3 pathlib_symlink_to.py
example_link
index.rst

文件类型

Path实例包含几种用于测试路径引用的文件类型的方法。本示例创建了多个不同类型的文件,并测试这些文件以及本地操作系统上可用的一些其他设备特定的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# pathlib_types.py
import itertools
import os
import pathlib
root = pathlib.Path('test_files')
# Clean up from previous runs.
if root.exists():
for f in root.iterdir():
f.unlink()
else:
root.mkdir()
# Create test files
(root / 'file').write_text(
'This is a regular file', encoding='utf-8')
(root / 'symlink').symlink_to('file')
os.mkfifo(str(root / 'fifo'))
# Check the file types
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()都没有参数。

1
2
3
4
5
6
7
8
9
$ 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()相同的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ python3 pathlib_stat.py
pathlib_stat.py:
Size: 607
Permissions: 0o100644
Owner: 527
Device: 16777218
Created : Thu Dec 29 12:25:25 2016
Last modified: Thu Dec 29 12:25:25 2016
Last accessed: Thu Dec 29 12:25:34 2016
$ python3 pathlib_stat.py index.rst
index.rst:
Size: 19363
Permissions: 0o100644
Owner: 527
Device: 16777218
Created : Thu Dec 29 11:27:58 2016
Last modified: Thu Dec 29 11:27:58 2016
Last accessed: Thu Dec 29 12:25:33 2016

要更简单地访问有关文件所有者的信息,使用owner()和group()。

1
2
3
4
5
6
7
# pathlib_ownership.py
import pathlib
p = pathlib.Path(__file__)
print('{} is owned by {}/{}'.format(p, p.owner(), p.group()))

当stat()返回数字系统ID值时,这些方法将查找与ID相关联的名称。

1
2
3
$ python3 pathlib_ownership.py
pathlib_ownership.py is owned by dhellmann/dhellmann

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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))

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

1
2
3
4
5
6
7
8
9
10
11
$ python3 pathlib_touch.py
creating new
Start: Thu Dec 29 12:25:34 2016
End : Thu Dec 29 12:25:35 2016
$ python3 pathlib_touch.py
already exists
Start: Thu Dec 29 12:25:35 2016
End : Thu Dec 29 12:25:36 2016

权限

在类Unix系统上,可以使用chmod()更改文件权限,将模式作为整数传递。模式值可以使用stat模块中定义的常量来构造。这个例子切换用户的执行权限位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# pathlib_chmod.py
import os
import pathlib
import stat
# Create a fresh test file.
f = pathlib.Path('pathlib_chmod_example.txt')
if f.exists():
f.unlink()
f.write_text('contents')
# Determine what permissions are already set using stat.
existing_permissions = stat.S_IMODE(f.stat().st_mode)
print('Before: {:o}'.format(existing_permissions))
# Decide which way to toggle them.
if not (existing_permissions & os.X_OK):
print('Adding execute permission')
new_permissions = existing_permissions | stat.S_IXUSR
else:
print('Removing execute permission')
# use xor to remove the user execute permission
new_permissions = existing_permissions ^ stat.S_IXUSR
# Make the change and show the new value.
f.chmod(new_permissions)
after_permissions = stat.S_IMODE(f.stat().st_mode)
print('After: {:o}'.format(after_permissions))

脚本假定它具有运行时修改文件模式所需的权限。

1
2
3
4
5
$ python3 pathlib_chmod.py
Before: 644
Adding execute permission
After: 744

删除

有两种从文件系统中删除东西的方法,具体取决于类型。要删除空目录,请使用rmdir()。

1
2
3
4
5
6
7
8
# pathlib_rmdir.py
import pathlib
p = pathlib.Path('example_dir')
print('Removing {}'.format(p))
p.rmdir()

如果后置条件已满足且目录不存在,则会引发FileNotFoundError异常。尝试删除非空的目录也是错误的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ 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.5/pathlib.py", line 1262, in rmdir
self._accessor.rmdir(self)
File ".../lib/python3.5/pathlib.py", line 371, in wrapped
return strfunc(str(pathobj), *args)
FileNotFoundError: [Errno 2] No such file or directory:
'example_dir'

对于文件,符号链接和大多数其他路径类型使用unlink()。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 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())

用户必须具有删除文件,符号链接,套接字或其他文件系统对象的权限。

1
2
3
4
$ python3 pathlib_unlink.py
exists before removing: True
exists after removing: False


参考资料

本文翻译自《The Python3 Standard Library By Example》pathlib相关章节

文章目录
  1. 1. 路径表示
  2. 2. 构建Paths
  3. 3. 解析Paths
  4. 4. 创建Concrete Paths
  5. 5. 目录内容
  6. 6. 读写文件
  7. 7. 操纵目录和符号链接
  8. 8. 文件类型
  9. 9. 文件属性
  10. 10. 权限
  11. 11. 删除
  12. 12. 参考资料
|