大家好,我是安果!
前段时间,在使用新版本的 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话不多说,先看下使用对比:比如说
使用 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 类,你还可以对此路径进行后续的操作,会很方便。
使用 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 更易读,更面向对象。
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 ]: 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 返回的是一个生成器,这在目录内文件特别多的时候可以大大节省内存,提升效率。
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' )]
这是 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'
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'
相比 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' )
>>> 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' ) '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)-10-cc7a0d86d4ed> in ----> 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 编程技巧。