为什么你应该开始习惯使用 pathlib?

几年前,当我发现 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/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,065评论 1 32
  • 两本不错的书: 《Python参考手册》:对Python各个标准模块,特性介绍的比较详细。 《Python核心编程...
    静熙老师哈哈哈阅读 3,350评论 0 80
  • 做到,总是比想到、知道难。 不喜欢今天的雨。本来该是阳光灿烂,银杏金黄,碧空如洗。结果天阴,雨落,人困。 是的,此...
    aa6a阅读 130评论 0 0
  • 一个朋友给我讲了个他身边真实发生的故事,我听了之后,心里凉凉的,哀其不幸,怒其不争,心里却还有些矛盾,是不是自己不...
    莫以唯阅读 458评论 0 2
  • 2018.8.22日,飞行1800多公里到达内蒙古鄂尔多斯!开启爱能商学院游学记! 第一天乘坐缆车进入沙漠 第一站...
    风洛真阅读 465评论 0 0