几年前,当我发现 Python
的新 pathlib
模块时,我最初认为它是 os.path
模块的一个稍微笨拙和不必要的面向对象版本。我错了。Python 的 pathlib 模块实际上很棒!
在本文中,我将尝试在pathlib上向你推销。我希望本文将激励你在任何需要使用 Python 中的文件时使用 Python 的 pathlib 模块。
os.path 笨拙
os.path 模块一直是我们用来处理 Python 中的路径的库。你需要的东西差不多都包含在内了,但有时它会很显得笨重。
你应该像这样导入它?
import os.path
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates')
还是像这样?
from os.path import abspath, dirname, join
BASE_DIR = dirname(dirname(abspath(__file__)))
TEMPLATES_DIR = join(BASE_DIR, 'templates')
或者,该 join 函数的命名过于笼统,我们还可以这样做:
from os.path import abspath, dirname, join as joinpath
BASE_DIR = dirname(dirname(abspath(__file__)))
TEMPLATES_DIR = joinpath(BASE_DIR, 'templates')
但是,我觉得这些都有点尴尬。我们将字符串传递到返回字符串的函数中,然后又将其传递给返回字符串的其他函数。这些字符串刚好可以表示路径,但它们仍然只是字符串。
当多个函数嵌套时,os.path 中字符串进字符串出类的函数非常笨拙,我们需要从内向外来阅读代码。如果我们可以把这些嵌套的函数调用转换成链式方法调用,这不是很好吗?
有了 pathlib
模块,我们就可以了!
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATES_DIR = BASE_DIR.joinpath('templates')
os.path 模块需要函数嵌套,但是 pathlib 模块的 path 类允许我们链式操作 Path 对象上的方法和属性,以获得等效的路径表示。
也许你在想:等等,这些路径对象不是一回事:它们是对象,不是路径字符串!不急,我们将稍后来讨论这个问题(提示:这些字符串几乎可以与路径字符串互换使用)。
os 模块臃肿
Python 经典模块 os.path
只用于处理路径。一旦你真的想通过路径做一些事情(例如创建一个目录),你就需要用到另一个 Python 模块,通常也是 os 下的模块。
os 模块有许多用于处理文件和目录的工具,比如:mkdir、getcwd、chmod、stat、remove、rename 和 rmdir。还有 chdir、link、wald、listdir、makedirs、rename、remvedirs、unlink (与remove相同) 以及 symlink。此外,还有一大堆与文件系统完全无关的东西:fork、getenv、putenv、environ、getlogin 和 system。还有很多在这里没有罗列的东西。
Python 的 os 模块什么都能做一点;它有点像是系统相关东西的大杂烩。尽管 os 模块中有很多不错的东西,但有时也可能很难找到你要的东西:比如你想在 os模块中查找与路径或文件系统相关的内容,则需要进一步挖掘。
pathlib 模块用 path 对象上的方法替换了许多这些与文件系统相关的 os 功能。
以下代码创建了src/_pypackages_
目录,并将 .editorconfig
文件重命名为 src/.editorconfig
:
import os
import os.path
os.makedirs(os.path.join('src', '__pypackages__'), exist_ok=True)
os.rename('.editorconfig', os.path.join('src', '.editorconfig'))
使用 Path 对象执行相同的操作:
from pathlib import Path
Path('src/__pypackages__').mkdir(parents=True, exist_ok=True)
Path('.editorconfig').rename('src/.editorconfig')
注意,由于链式方法,pathlib 代码将路径放在第一位!
正如 Python 之禅所说,“名称空间是一个很棒的想法,让我们做更多的事情”。os 模块是一个非常大的名称空间,里面有一堆东西。pathlib.path
类是一个比 os 模块更小、目标更明确的命名空间。此路径命名空间中的方法返回路径对象,允许方法链式操作而不是字符串拼接式的嵌套函数调用。
别忘了还有 GLOB 模块!
os 和 os.path 模块并不是 Python 标准库中唯一与文件路径/文件系统相关的功能模块。glob 模块是另一个处理路径相关的模块。
我们可以使用 glob.glob
函数查找与特定模式匹配的文件:
from glob import glob
top_level_csv_files = glob('*.csv')
all_csv_files = glob('**/*.csv', recursive=True)
新的 pathlib 模块同样包括类似 glob 的功能。
from pathlib import Path
top_level_csv_files = Path.cwd().glob('*.csv')
all_csv_files = Path.cwd().rglob('*.csv')
当重度使用 pathlib 之后,你可能会完全忘记 glob 模块的存在: PATH 对象已经提供了的所有 glob 模块所具备的功能。
pathlib 让简单变得更简单
pathlib 模块将许多复杂的情况变得简单,但它也可以使一些简单的事情变得更简单。
需要读取一个或多个文件中的所有文本?
你可以使用 with 语句块打开文件、读取其内容然后关闭文件:
from glob import glob
file_contents = []
for filename in glob('**/*.py', recursive=True):
with open(filename) as python_file:
file_contents.append(python_file.read())
或者,你可以用 Path 对象的 read_text 方法,在一行代码中用列表解析功能将文件内容读取到一个新列表中:
from pathlib import Path
file_contents = [
path.read_text()
for path in Path.cwd().rglob('*.py')
]
如果我需要写入文件呢?
你可以使用 open 上下文管理器:
with open('.editorconfig') as config:
config.write('# config goes here')
或者使用 write_text 方法:
Path('.editorconfig').write_text('# config goes here')
如果你更喜欢使用 open (无论是作为上下文管理器还是其他方式),你同样可以在 PATH 对象上使用 OPEN 方法:
from pathlib import Path
path = Path('.editorconfig')
with path.open(mode='wt') as config:
config.write('# config goes here')
或者,从 Python3.6 开始,你甚至可以将 PATH 对象传递给内置的 open 函数:
from pathlib import Path
path = Path('.editorconfig')
with open(path, mode='wt') as config:
config.write('# config goes here')
PATH 对象让你的代码更加明确
以下三个变量指向什么?它们的值代表什么?
person = '{"name": "Trey Hunner", "location": "San Diego"}'
pycon_2019 = "2019-05-01"
home_directory = '/home/trey'
这些变量中的每一个都指向一个字符串。
这些字符串表示不同的东西:一个是 JSON blob,一个是日期,还有一个是文件路径。
以下是这些对象更有用的表示:
from datetime import date
from pathlib import Path
person = {"name": "Trey Hunner", "location": "San Diego"}
pycon_2019 = date(2019, 5, 1)
home_directory = Path('/home/trey')
JSON 对象可以反序列化到字典,日期在本地使用 datetime.date 对象表示,文件系统路径现在可以使用 pathlib.path 对象统一表示。
使用 Path 对象可以使代码更加明确。如果要表示日期,则可以使用 Date 对象。如果试图表示文件路径,则可以使用 Path 对象。
我并非面向对象编程的坚定拥护者。类增加了另一层抽象层,而抽象有时会增加更多的复杂性无法保持简单化。但是 pathlib.Path 类是一个有用的抽象。它也正在迅速成为一个普遍接受的抽象。
感谢 PEP519,文件路径对象现在成为使用路径的标准。在 Python3.6 中,内置的 open 函数以及 os、shutil 和 os.path 模块中的各种函数都可以与 pathlib.path 对象一起正常工作。你可以从现在开始使用 pathlib,而不需要改动大多数使用路径的代码!
pathlib 还缺少什么?
虽然 pathlib 是伟大的,但它并不是包罗万象的。我无意中发现了一些缺失的特性,我希望 pathlib 模块能够包括这些特性。
我注意到的第一个缺陷是 pathlib.Path 的方法中缺少与 shutil 等效的功能。
虽然可以将路径对象(和类似路径的对象)传递给高层的 shutil 函数进行复制/删除/移动文件和目录的操作,但是 Path 对象上没有与此等效的方法。
因此,要复制文件,你仍然需要执行如下操作:
from pathlib import Path
from shutil import copyfile
source = Path('old_file.txt')
destination = Path('new_file.txt')
copyfile(source, destination)
同样也没有与 os.chdir 等效的 pathlib 功能。
这意味着如果需要更改当前工作目录,仍需要导入 chdir:
from pathlib import Path
from os import chdir
parent = Path('..')
chdir(parent)
也没有与 os.walk 函数等价的 pathlib 函数。尽管你可以很容易地使用 pathlib 创建你自己的 walk
式函数。
我希望 pathlib.Path 对象最终可以包含其中一些缺失操作的方法。尽管存在这些缺失的特性,我仍然觉得使用“ pathlib 系”比使用“ os.path 系”更易于管理。
你是否应该始终使用 pathlib?
从Python3.6开始,pathlib.Path 对象几乎可以在任何已经使用路径字符串的地方工作。因此,如果你使用的是Python3.6(或更高版本),我认为没有理由不使用 pathlib。
如果您使用的是 python 3 的早期版本, 则可以将 path 对象始终包装在 str 调用中, 以便在需要转义填充字符串的地方获取相应的字符串返回。虽然有点尴尬, 但还是管用:
from os import chdir
from pathlib import Path
chdir(Path('/home/trey')) # Python 3.6+ 版
chdir(str(Path('/home/trey'))) # 早期的 Python 3 版
不管你使用的是哪个版本的 Python3,我都建议你尝试一下 pathlib。
什么?你还在用 Python2 ?好吧,万能的 PyPI 上有个第三方工具 pathlib2 模块是一个选择,现在,你可以在任何版本的 Python 上使用 pathlib 啦!。
用 pathlib 让我的代码更具可读性。现在,我处理文件的大多数代码都默认使用 pathlib,我建议你也这样做。如果可以用 pathlib,则尽量用吧。
原文:https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/
网友评论