求求你,别用 os.path 了

Python七号

共 9293字,需浏览 19分钟

 ·

2021-05-11 20:11

前段时间,在使用新版本的 Django 时,我发现了 settings.py 的第一行代码从

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

变成了

from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent

于是我就好奇,os 和 pathlib 同样是标准库,为什么 pathlib 得到了 Django 的青睐?学习了一番 pathlib 之后,发现这是一个非常高效便捷的工具,用它来处理文件系统路径相关的操作最合适不过,集成了很多快捷的功能,提升你的编程效率,那是妥妥的。

接下来让一起看一下,为什么 pathlib 更值得我们使用。

pathlib vs os

话不多说,先看下使用对比:比如说

  1. 打印当前的路径:

使用 os:

In [13]: import os

In [14]: os.getcwd()
Out[14]: '/Users/aaron'

使用 pathlib:

In [15]: from pathlib import Path

In [16]: Path.cwd()
Out[16]: PosixPath('/Users/aaron')
In [17]: print(Path.cwd())
/Users/aaron

使用 print 打印的结果是一样的,但 os.getcwd() 返回的是字符串,而 Path.cwd() 返回的是 PosixPath 类,你还可以对此路径进行后续的操作,会很方便。

  1. 判断路径是否存在:

使用 os:

In [18]: os.path.exists("/Users/aaron/tmp")
Out[18]: True

使用 pathlib:

In [21]: tmp = Path("/Users/aaron/tmp")

In [22]: tmp.exists()
Out[22]: True

可以看出 pathlib 更易读,更面向对象。

  1. 显示文件夹的内容
In [38]: os.listdir("/Users/aaron/tmp")
Out[38]: ['.DS_Store''.hypothesis''b.txt''a.txt''c.py''.ipynb_checkpoints']

In [39]: tmp.iterdir()
Out[39]: <generator object Path.iterdir at 0x7fa3f20d95f0>

In [40]: list(tmp.iterdir())
Out[40]:
[PosixPath('/Users/aaron/tmp/.DS_Store'),
 PosixPath('/Users/aaron/tmp/.hypothesis'),
 PosixPath('/Users/aaron/tmp/b.txt'),
 PosixPath('/Users/aaron/tmp/a.txt'),
 PosixPath('/Users/aaron/tmp/c.py'),
 PosixPath('/Users/aaron/tmp/.ipynb_checkpoints')]

可以看出 Path().iterdir 返回的是一个生成器,这在目录内文件特别多的时候可以大大节省内存,提升效率。

  1. 通配符支持

os 不支持含有通配符的路径,但 pathlib 可以:

In [45]: list(Path("/Users/aaron/tmp").glob("*.txt"))
Out[45]: [PosixPath('/Users/aaron/tmp/b.txt'), PosixPath('/Users/aaron/tmp/a.txt')]
  1. 便捷的读写文件操作

这是 pathlib 特有的:

f = Path('test_dir/test.txt'))
f.write_text('This is a sentence.')
f.read_text()

也可以使用 with 语句:

>>> p = Path('setup.py')
>>> with p.open() as f: f.readline()
...
'#!/usr/bin/env python3\n'
  1. 获取文件的元数据
In [56]: p = Path("/Users/aaron/tmp/c.py")

In [57]: p.stat()
Out[57]: os.stat_result(st_mode=33188, st_ino=35768389, st_dev=16777221, st_nlink=1, st_uid=501, st_gid=20, st_size=20, st_atime=1620633580, st_mtime=1620633578, st_ctime=1620633578)

In [58]: p.parts
Out[58]: ('/''Users''aaron''tmp''c.py')

In [59]: p.parent
Out[59]: PosixPath('/Users/aaron/tmp')

In [60]: p.resolve()
Out[60]: PosixPath('/Users/aaron/tmp/c.py')

In [61]: p.exists()
Out[61]: True

In [62]: p.is_dir()
Out[62]: False

In [63]: p.is_file()
Out[63]: True

In [64]: p.owner()
Out[64]: 'aaron'

In [65]: p.group()
Out[65]: 'staff'

In [66]: p.name
Out[66]: 'c.py'

In [67]: p.suffix
Out[67]: '.py'

In [68]: p.suffixes
Out[68]: ['.py']

In [69]: p.stem
Out[69]: 'c'

  1. 路径的连接 join

相比 os.path.join,使用一个 / 是不是更为直观和便捷?

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

当然,也可以使用 joinpath 方法

>>> PurePosixPath('/etc').joinpath('passwd')
PurePosixPath('/etc/passwd')
>>> PurePosixPath('/etc').joinpath(PurePosixPath('passwd'))
PurePosixPath('/etc/passwd')
>>> PurePosixPath('/etc').joinpath('init.d''apache2')
PurePosixPath('/etc/init.d/apache2')
>>> PureWindowsPath('c:').joinpath('/Program Files')
PureWindowsPath('c:/Program Files')
  1. 路径匹配
>>> PurePath('a/b.py').match('*.py')
True
>>> PurePath('/a/b/c.py').match('b/*.py')
True
>>> PurePath('/a/b/c.py').match('a/*.py')
False

pathlib 出现的背景和要解决的问题

pathlib 目的是提供一个简单的类层次结构来处理文件系统的路径,同时提供路径相关的常见操作。那为什么不使用 os 模块或者 os.path 来实现呢?

许多人更喜欢使用 datetime 模块提供的高级对象来处理日期和时间,而不是使用数字时间戳和 time 模块 API。同样的原因,假如使用专用类表示文件系统路径,也会更受欢迎。

换句话说,os.path 是面向过程风格的,而 pathlib 是面向对象风格的。Python 也在一直在慢慢地从复制 C 语言的 API 转变为围绕各种常见功能提供更好,更有用的抽象。

其他方面,使用专用的类处理特定的需求也是很有必要的,例如 Windows 路径不区分大小写。

在这样的背景下,pathlib 在 Python 3.4 版本加入标准库。

pathlib 的优势和劣势分别是什么

pathlib 的优势在于考虑了 Windows 路径的特殊性,同时提供了带 I/O 操作的和不带 I/O 操作的类,使用场景更加明确,API 调用更加易懂。

先看下 pathlib 对类的划分:

图中的箭头表示继承自,比如 Path 继承自 PurePath,PurePath 表示纯路径类,只提供路径常见的操作,但不包括实际 I/O 操作,相对安全;Path 包含 PurePath 的全部功能,包括 I/O 操作。

PurePath 有两个子类,一个是 PureWindowsPath,表示 Windows 下的路径,不区分大小写,另一个是 PurePosixPath,表示其他系统的路径。有了 PureWindowsPath,你可以这样对路径进行比较:

from pathlib import PureWindowsPath
>>> PureWindowsPath('a') == PureWindowsPath('A')
True

PurePath 可以在任何操作系统上实例化,也就是说与平台无关,你可以在 unix 系统上使用 PureWindowsPath,也可以在 Windows 系统上使用 PurePosixPath,他们还可以相互比较。

>>> from pathlib import PurePosixPath, PureWindowsPath, PosixPath  
>>> PurePosixPath('a') == PurePosixPath('b')
False
>>> PurePosixPath('a') < PurePosixPath('b')
True
>>> PurePosixPath('a') == PosixPath('a')
True
>>> PurePosixPath('a') == PureWindowsPath('a')
False

可以看出,同一个类可以相互比较,不同的类比较的结果是 False。

相反,包含 I/O 操作的类 PosixPath 及 WindowsPath 只能在对应的平台实例化:

In [8]: from pathlib import PosixPath,WindowsPath

In [9]: PosixPath('a')
Out[9]: PosixPath('a')

In [10]: WindowsPath('a')
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-10-cc7a0d86d4ed> in <module>
----> 1 WindowsPath('a')

/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/pathlib.py in __new__(cls, *args, **kwargs)
   1038         self = cls._from_parts(args, init=False)
   1039         if not self._flavour.is_supported:
-> 1040             raise NotImplementedError("cannot instantiate %r on your system"
   1041                                       % (cls.__name__,))
   1042         self._init()

NotImplementedError: cannot instantiate 'WindowsPath' on your system

In [11]:

要说劣势,如果有的话,那就是在选择类时会比较困惑,到底用哪一个呢?其实如果你不太确定的话,用 Path 就可以了,这也是它的名称最短的原因,因为更加常用,短点的名称编写的更快。

适用的场景

如果要处理文件系统相关的操作,选 pathlib 就对了。

一些关键点

获取家目录:

In [70]: from pathlib import Path

In [71]: Path.home()
Out[71]: PosixPath('/Users/aaron')

父目录的层级获取:

>>> 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:/')

获取多个文件后缀:

>>> PurePosixPath('my/library.tar.gar').suffixes
['.tar''.gar']
>>> PurePosixPath('my/library.tar.gz').suffixes
['.tar''.gz']
>>> PurePosixPath('my/library').suffixes
[]


Windows 风格转 Posix:

>>> p = PureWindowsPath('c:\\windows')
>>> str(p)
'c:\\windows'
>>> p.as_posix()
'c:/windows'

获取文件的 uri:

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

判断是否绝对路径:

>>> PurePosixPath('/a/b').is_absolute()
True
>>> PurePosixPath('a/b').is_absolute()
False

>>> PureWindowsPath('c:/a/b').is_absolute()
True
>>> PureWindowsPath('/a/b').is_absolute()
False
>>> PureWindowsPath('c:').is_absolute()
False
>>> PureWindowsPath('//some/share').is_absolute()
True

文件名若有变化:

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

是不是非常方便?

技术的底层原理和关键实现

pathlib 并不是基于 str 的实现,而是基于 object 设计的,这样就严格地区分了 Path 对象和字符串对象,同时也用到了一点 os 的功能,比如 os.name,os.getcwd 等,这一点大家可以看 pathlib 的源码了解更多。

最后的话

本文分享了 pathlib 的用法,后面要处理路径相关的操作时,你应该第一时间想到 pathlib,不会用没有关系,搜索引擎所搜索 pathlib 就可以看到具体的使用方法。

虽然 pathlib 比 os 库更高级,更方便并且提供了很多便捷的功能,但是我们仍然需要知道如何使用 os 库,因为 os 库是 Python 中功能最强大且最基本的库之一,但是,在需要一些文件系统操作时,强烈建议使用 pathlib。

关注我,学习更多 Python 编程技巧。



浏览 45
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报