使用 pathlib 更轻松地处理 Python 路径
了解使用 pathlib(“面向对象的方式处理路径”)的好处。
处理文件是开发人员最常做的事情之一。毕竟,您经常需要从文件中读取(读取其他用户、会话或程序保存的信息)或写入文件(记录其他用户、会话或程序的数据)。
当然,文件位于目录中。浏览目录、在这些目录中查找文件,甚至提取有关目录(及其中的文件)的信息可能很常见,但处理起来往往令人沮丧。在 Python 中,许多不同的模块和对象提供了此类功能,包括 os.path、os.stat 和 glob。
这不一定不好;事实是,Python 开发人员已经使用模块、方法和文件的这种组合很长时间了。但是,如果您曾经觉得它有点笨拙或过时,那么您并不孤单。
实际上,事实证明,几年前,Python 的标准库就已经附带了 pathlib 模块,这使得处理目录和文件变得更加容易。我说“事实证明”,是因为尽管我可能是一位资深的开发人员和讲师,但我只是在过去几个月才发现了“pathlib”——我必须承认,我完全被它迷住了。
pathlib 被描述为一种面向对象的方式来处理路径,这种描述对我来说似乎非常贴切。您不是使用字符串,而是使用“Path”对象,这不仅允许您将所有喜欢的路径和文件相关的功能用作方法,而且还允许您弥合操作系统之间的差异。
因此,在本文中,我将介绍 pathlib,比较您以前可能做的事情与 pathlib 现在允许您做的事情。
pathlib 基础知识如果您想使用 pathlib,您需要将其加载到您的 Python 会话中。您应该从以下内容开始
import pathlib
请注意,如果您计划定期使用 pathlib 中的某些名称,您可能需要使用 from-import
。但是,我强烈建议不要使用 from pathlib import *
,这样做确实可以带来将模块的所有名称导入当前命名空间的好处,但也会带来将模块的所有名称导入当前命名空间的负面影响。简而言之,只导入您需要的内容。
现在您已经完成了该操作,您可以创建一个新的 Path 对象。这允许您表示文件或目录。您可以使用字符串创建它,就像在更传统的 Python 代码中执行路径(或文件名)一样
p2 = pathlib.Path('.')
但是请稍等。您是否使用 pathlib.Path
来表示文件或目录?答案是“是”。您实际上可以将它用于两者。如果您不确定您拥有哪种类型的对象,您始终可以使用 is_dir
和 is_file
方法来询问它
>>> p1 = pathlib.Path('hello.py')
>>> p2 = pathlib.Path('.')
>>> p1.is_file()
True
>>> p2.is_file()
False
>>> p1.is_dir()
False
>>> p2.is_dir()
True
请注意,仅仅因为您创建了一个 Path 对象并不意味着文件或目录实际存在。您可以使用 exists
方法进行检查
>>> p1 = pathlib.Path('hello.py')
>>> p1.exists()
True
>>> p2 = pathlib.Path('asdfafafsafaa')
>>> p2.exists()
False
操作路径
假设您想在目录 /foo/bar 中处理一个名为 abc.txt 的文件。在典型的 Python 程序中,您会这样说
open('/foo/bar' + 'abc.txt')
您在这里没有做任何特别令人兴奋的事情;您只是将两个字符串连接在一起,第一个字符串表示目录,第二个字符串表示文件。但是正如您所看到的,已经存在一个问题,即您没有使用 / 分隔目录和文件名。
您可以使用 os.path.join
来避免此类问题
>>> import os.path
>>> dirname = '/foo/bar'
>>> filename = 'abc.txt'
>>> os.path.join(dirname, filename)
'/foo/bar/abc.txt'
使用 os.path.join
不仅确保在您需要的地方有斜杠,而且它还可以跨平台工作,如果您的程序在 Windows 系统上运行,则使用 \。
这很好,但 pathlib 提供了另一个选项:您可以使用 / 运算符(通常用于除法)将路径连接在一起。例如
>>> dirname = pathlib.Path('/foo/bar')
>>> dirname / filename
PosixPath('/foo/bar/abc.txt')
习惯在您可能认为是字符串的内容之间看到 / 需要一段时间。但请记住,dirname
不是字符串;相反,它是一个 Path 对象。而 / 是一个 Python 运算符,这意味着它可以为不同的类型重载和重新定义。
如果您忘记了并尝试将您的 Path 对象视为字符串,Python 会提醒您
>>> dirname + filename
TypeError: unsupported operand type(s) for +: 'PosixPath'
↪and 'str'
处理目录
如果您的 Path 对象包含目录,则可以在其上运行许多与目录相关的方法。实际上,您也可以在非目录 Path 对象上运行这些方法,但它不会以非常有用的方式结束。
例如,假设您想查找当前目录中的所有文件。您可以说
>>> p = pathlib.Path('.')
>>>
>>> p.iterdir()
<generator object Path.iterdir at 0x111e4b1b0>
请注意,调用 p.iterdir()
的结果是一个生成器对象。您可以将此类对象放在 for
循环或其他期望/需要迭代的上下文中。生成器将为目录中的每个文件名返回一个值。
但是,如果您对获取所有文件名不感兴趣怎么办?如果您只想获取那些以 .py 结尾的文件怎么办?如果您在 UNIX shell 中工作,您会说类似 ls *.py
的内容。尽管许多人认为,但这种模式不是正则表达式。相反,这种模式被称为“globbing”。Python 中的 glob
模块为您处理该问题,让您可以说类似
import glob
glob.glob('*.py')
调用 glob.glob
的结果是一个字符串列表,每个字符串包含一个与模式匹配的文件名。
由于 glob
方法,Path 对象具有类似的功能。与 iterdir
类似,glob
方法返回一个生成器,这意味着您可以在 for
循环中使用它。例如
>>> p.glob('*.py')
<generator object Path.glob at 0x111b38480>
>>> for one_item in p.glob('*.py'):
print(f"{one_item}: {type(one_item)}")
hello.py: <class 'pathlib.PosixPath'>
reverse_lines.py: <class 'pathlib.PosixPath'>
old_test_hello.py: <class 'pathlib.PosixPath'>
好消息是您可以获得目录中的文件名。并且文件名已经通过 glob
过滤,因此您只获得匹配项。更好的消息是,您获得的是 Path 对象(在本例中为 PosixPath
对象,因为此示例不在 UNIX 系统上),这意味着您可以使用迄今为止您喜欢的所有技巧。
一旦您有一个文件,您可以用它做什么?嗯,一个明显的选择是打开它并读取其内容。您可以使用 read_bytes
和 read_text
方法来执行此操作,它们分别返回“bytes”和字符串对象。
请注意,与您通常可以在 Python 中的“file”对象上运行的 read
方法不同,read_text
和 read_bytes
都会打开文件,检索其内容并再次关闭它。因此,您不必担心内部文件指针位于何处,或者您是从文件开头还是其他位置读取。
但是,如果您从特别大的文件中读取,这些方法可能会导致问题。Python 会很乐意尽可能多地读取到一个巨大的字符串中,可能会使用计算机上的所有(或大部分)内存。
更好的策略,也是 Python 中的传统策略,是一次读取文件内容的一行。这可以通过将打开的“file”对象放入 for
循环来完成;文件对象是可迭代的,并在每次迭代中返回一行(即,直到并包括以下换行符)。
请注意,虽然您当然可以使用内置的 open
函数,但您也可以利用 Path
对象的 open
方法
>>> p = pathlib.Path('hello.py')
>>> for one_line in p.open():
>>> print(one_line)
这将打印文件中的所有行。请注意,open
知道如何像处理字符串一样轻松地处理 Path 对象。但是,您还会注意到,当您打印文件时,行是双倍间距的。这是因为每次迭代都包含换行符,并且 print
还在其打印的每一行之后插入一个换行符。您可以通过将空字符串传递给 print
函数中的 end
参数来调整此设置
>>> for one_line in p.open():
>>> print(one_line, end='')
除了打开文件之外,您还可以在 Path 对象上调用许多其他方法。例如,我之前提到过,您可能不想将整个大文件读入内存。您可以使用 stat
方法检查文件的大小以及许多其他属性。此方法与传统的 os.stat
Python 函数一样,以字节为单位返回文件的大小
>>> p.stat().st_size
123
您同样可以检索 stat
报告的其他项目,包括文件最近修改的时间戳,以及拥有该文件的用户和组的 ID。
如果您想操作文件名,您可以使用诸如 suffix
之类的方法来执行此操作
>>> p.suffix()
'.py'
结论
如果您经常从 Python 程序中处理文件,我建议您查看 pathlib。它不是革命性的,但它确实有助于将许多文件操作代码集中到一个地方。此外,/ 语法虽然一开始看起来很奇怪,但它强调了您正在处理 Path 对象,而不是字符串这一事实。此外,无需记住功能的位置即可访问如此多的功能非常方便。
资源pathlib 最初在 PEP 428 中提出(并被接受),值得在此处阅读。它自 Python 3.4 以来就已存在。如果您仍在使用 Python 2.7,则 PyPI 上提供了一个带有向后移植的软件包,称为 pathlib2。