Python3的pathlib模块:驯服文件系统

2021-07-04 02:06

阅读:672


目录

  • python文件路径处理时的问题

  • 创建路径

  • 读写文件

  • 找出路径的组成部分

  • 移动和删除文件

  • 例子

    • 计算文件数量

    • 显示目录树

    • 查找上次修改的文件

    • 创建唯一的文件名

  • 操作系统的差别

  • 以合适的对象表现路径

  • 结论

你是否遇到过使用python处理文件路径时的麻烦呢?在python 3.4以及更高版本中,这些麻烦不复存在。不再需要像下面这样从头开始编写代码:

技术分享图片

或者编写下面这样冗长烦人的代码:

技术分享图片

在这篇文章中,你将看到怎样使用python处理文件路径-目录和文件的名字。

技术分享图片

python文件路径处理时的问题

处理文件和与文件系统交互很重要,原因很多。 最简单的情况可能只涉及读取或写入文件,但有时会是更复杂的任务。也许您需要列出目录中属于给定类型的的所有文件,查找给定文件的父目录,或者创建一个不存在的唯一文件名。

习惯上,Python使用常规文本字符串表示文件路径。 在os.path标准库的支持下,尽管有点麻烦,但这已经足够了(正如引言中的第二个例子所示)。然而,由于路径不是字符串,重要的功能遍布在标准库中,包括像os,glob和shutil这样的库。 下面的示例需要三个导入语句,以便将所有文本文件移动到归档目录:

技术分享图片

对于由字符串表示的路径,使用常规字符串方法是可能的,但通常是一个坏主意。 例如,你应该使用os.path.join来链接两条路径,它使用操作系统上正确的路径分隔符来链接路径,而不是像普通的字符串那样使用“+”。 回想一下Windows使用,而Mac和Linux使用/作为分隔符。 这种差异可能会导致难以发现的错误,例如我们介绍的第一个示例仅适用于Windows路径。

Python 3.4中引入了pathlib模块(PEP 428)来处理这些挑战。 它在一个地方收集必要的功能,并通过易于使用的Path对象上的方法和属性使其可用。

早期,其他软件包仍然使用字符串作为文件路径,但从Python 3.6开始,pathlib模块在整个标准库中得到支持,部分原因是由于增加了文件系统路径协议。 如果你坚持使用传统的Python,那么Python 2也有一个可用的向后移植。

实践时间:让我们看看pathlib如何在实践中发挥作用。

创建路径

你真正需要知道的是pathlib.Path类。 创建路径有几种不同的方式。 首先,有类方法,如.cwd(当前工作目录)和.home(用户的主目录):

技术分享图片

注意:在本教程中,我们将假定已经导入了pathlib,而没有像上面那样给出import pathlib。 由于您将主要使用Path类,因此您也可以使用语句from pathlib import Path并使用Path而不是pathlib.Path。

还可以从其字符串表示中显式创建路径:

技术分享图片

处理Windows路径的小技巧:在Windows上,路径分隔符是反斜杠。 但是,在许多情况下,反斜杠也用作转义字符以表示不可打印的字符。 为避免出现问题,请使用原始字符串来表示Windows路径。 这些字符串,它们前面都有一个r。 在原始字符串中,表示文字反斜线:r"C:Users"。

构建路径的第三种方法是使用特殊运算符“/”链接路径的各个部分。 正斜杠操作符独立于平台上的实际路径分隔符使用:

技术分享图片

只要至少有一个Path对象,/就可以连接多个路径或路径和字符串的混合(如上所述)。 如果你不喜欢特殊/符号,你可以用.joinpath方法做同样的事情:

技术分享图片

请注意,在前面的示例中,pathlib.Path由WindowsPath或PosixPath表示。 表示路径的实际对象取决于底层操作系统。 (也就是说,在Windows上结果表现为一个WindowsPath对象,而在Mac或Linux上结果表现为一个PosixPath对象。)有关更多信息,请参阅操作系统差异部分。

读写文件

习惯上,使用Python读取或写入文件的方法是使用内置的open函数。 这仍然是正确的,因为open函数可以直接使用Path对象。 以下示例查找Markdown文件中的所有标题并打印它们:

技术分享图片

一个等同的选择是在Path对象上调用.open:

技术分享图片

实际上,Path.open在后台调用内置的open。 你使用哪个选项主要是品味的问题。

对于简单的文件读写,在pathlib库中有几个简便的方法:

  • .read_text: 以文本模式打开路径并并以字符串形式返回内容。

  • .read_bytes: 以二进制/字节模式打开路径并以字节串的形式返回内容。

  • .write_text: 打开路径并向其写入字符串数据。

  • .write_bytes: 以二进制/字节模式打开路径并向其写入数据。

这些方法中的每一个都能处理文件的打开和关闭,使用起来非常简便,例如:

技术分享图片

路径也可以指定为简单的文件名,在这种情况下,它们是相对于当前工作目录进行解析的。 以下示例等同于上一个示例:

技术分享图片

.resolve方法将找到完整路径。 下面,我们确认当前工作目录用于简单文件名:

技术分享图片

请注意,比较路径时,比较的是它们的表示方式。 在上面的例子中,path.parent不等于pathlib.Path.cwd,因为path.parent用"."表示。 而pathlib.Path.cwd由"/home/gahjelle/realpython/"表示。

找出路径的组成部分

路径的不同部分可以方便地用作属性。 基本的例子包括:

  • .name: 没有任何目录的文件名

  • .parent: 包含该文件的目录,或者如果path是目录,则是父目录

  • .stem: 文件名不带后缀

  • .suffix: 文件扩展名

  • .anchor: 目录之前的路径部分

以下是这些属性的作用:

技术分享图片

请注意.parent返回一个新的Path对象,而其他属性返回字符串。 这意味着,例如,可以像上一个示例那样链接.parent,甚至可以与/组合创建完全新的路径:

技术分享图片

优秀的Pathlib备忘录提供了所有属性和方法的可视化表示。

移动和删除文件

通过pathlib,您还可以访问基本的文件系统级操作,如移动,更新甚至删除文件。 大多数情况下,这些方法不会在信息或文件丢失之前发出警告或等待确认。 使用这些方法时要小心。

要移动文件,请使用.replace。 请注意,如果目标已存在,.replace将覆盖它。 不幸的是,pathlib没有明确支持安全移动文件。 为了避免可能覆盖目标路径,最简单的方法是在更换之前测试目标是否存在:

技术分享图片

然而,这确实为可能的资源竞争留下隐患。 另一个进程可能会在执行if语句和.replace方法之间的目标路径中添加一个文件。 如果这是一个问题,更安全的方法是打开独占创建的目标路径并明确复制源数据:

技术分享图片

如果目标已经存在,上面的代码将引发FileExistsError。 从技术上讲,这复制了一个文件。 要执行移动,只需在完成复制后删除源(请参阅下文)。 确保没有发生异常。

重命名文件时,有用的方法可能是.with_name和.with_suffix。 它们都返回原始路径,但分别替换名称或后缀。

例如:

技术分享图片

目录和文件可分别使用.rmdir和.unlink删除。 (再次提醒,要非常小心!)

例子

在本节中,您将看到一些如何使用pathlib来处理简单挑战的示例。

计算文件数量

列出大量文件有几种不同的方法。 最简单的是.iterdir方法,它遍历给定目录中的所有文件。 以下示例将.iterdir与collections.Counter类组合起来,以统计当前目录中每个文件类型的文件数量:

技术分享图片

使用方法.glob和.rglob(递归glob)可以创建更灵活的文件列表。 例如,pathlib.Path.cwd。glob("*.txt")返回当前目录中所有带有.txt后缀的文件。 以下仅计算以p开头的文件类型:

技术分享图片

显示目录树

下一个示例定义了一个函数tree,它将打印一个表示文件层次结构的可视化树,该树以根目录为根。 在这里,我们也想列出子目录,所以我们使用.rglob方法:

技术分享图片

请注意,我们需要知道文件所在的目录到根目录有多远。 为此,我们首先使用.relative_to来表示相对于根目录的路径。 然后,我们计算表示中的目录数量(使用.parts属性)。 运行时,此函数创建如下的可视化树:

技术分享图片
  • 注意:f-strings只能在Python 3.6及更高版本中使用。 在较老的Pythons中,表达式f"{spacer} + {path.name}"可以写为"{0} + {1}".format(spacer,path.name)。

查找上次修改的文件

.iterdir,.glob和.rglob方法非常适合生成器表达式和列表解析。 要在上次修改的目录中查找文件,可以使用.stat方法获取有关底层文件的信息。 例如,.stat.st_mtime给出了文件上次修改的时间:

技术分享图片

您甚至可以使用类似的表达式获取上次修改的文件的内容:

技术分享图片

从不同的.stat.st_属性返回的时间戳表示1970年1月1日以来的秒数。除了datetime.fromtimestamp之外,time.localtime或time.ctime可用于将时间戳转换为更加可用的内容。

创建一个唯一的文件名

最后一个例子将显示如何基于模板构建唯一编号的文件名。 首先,为文件名指定一个模式,并为计数器留出空间。 然后,检查通过加入目录和文件名创建的文件路径是否存在(使用计数器的值)。 如果它已经存在,请增加计数器并重试:

技术分享图片

如果该目录已经包含文件test001.txt和test002.txt,则上述代码将设置路径为test003.txt。

操作系统差异

早些时候,我们注意到当我们实例化pathlib.Path时,返回了WindowsPath或PosixPath对象。 对象的种类取决于您使用的操作系统。 该功能使编写跨平台兼容代码变得相当容易。 可以明确地询问WindowsPath或PosixPath,但您只会将代码限制在该系统中,没有任何好处。 这样的具体路径不能用于不同的系统:

技术分享图片

有时可能需要表示路径而不访问底层文件系统(在这种情况下,在非Windows系统上表示Windows路径也是有意义的,反之亦然)。 这可以使用PurePath对象完成。 这些对象支持路径组件部分讨论的操作,但不支持访问文件系统的方法:

技术分享图片

您可以直接在所有系统上实例化PureWindowsPath或PurePosixPath。 根据您使用的操作系统,实例化PurePath将返回其中一个对象。

作为合适对象的路径

在简介中,我们简单地指出路径不是字符串,pathlib背后的一个动机是用适当的对象表示文件系统。 事实上,pathlib的官方文档标题为pathlib--面向对象的文件系统路径。 在上面的例子中,面向对象的方法已经非常明显(特别是如果你将它与旧的os.path方式做对比)。 但是,让我给你留下一些其他小知识。

与正在使用的操作系统无关,路径以Posix样式表示,正斜杠作为路径分隔符。 在Windows上,您将看到如下所示的内容:

技术分享图片

但是,当路径转换为字符串时,它将使用本地形式,例如在Windows上使用反斜杠:

技术分享图片

如果您正在使用不知道如何处理pathlib.Path对象的库,这特别有用。 这是3.6之前的Python版本中的一个大问题。 例如,在Python 3.5中,configparser标准库只能使用字符串路径来读取文件。 处理这种情况的方法是明确地转换为字符串:

技术分享图片

在Python 3.6及更高版本中,如果需要进行显式转换,建议使用os.fspath而不是str。 这样会更安全一些,因为如果您偶然尝试转换不是路径的对象,它会引发错误。

可能是pathlib库最不寻常的部分是使用/运算符。 窥探一下源码,让我们看看它是如何实现的。 这是运算符重载的一个例子:运算符的行为根据上下文而改变。 你以前见过这个。 想想+对于字符串和数字来说意味着不同的东西。 Python通过使用双下划线方法(a.k.a. dunder方法)来实现运算符重载。

/运算符由.__ truediv __方法定义。 事实上,如果你看看pathlib的源代码,你会看到类似于:

技术分享图片

结论

自Python 3.4以来,pathlib已经在标准库中可用。 使用pathlib,文件路径可以用适当的Path对象来表示,而不像以前那样用普通的字符串表示。 这些对象使代码处理文件路径:

  • 易于阅读,尤其是因为/用于将路径连接在一起

  • 功能更强大,可直接在对象上使用最必要的方法和属性

  • 在操作系统中更一致,因为不同系统的特性被Path对象隐藏

英文原文:https://realpython.com/python-pathlib/
译者:javylee


评论


亲,登录后才可以留言!