Python-自动化指南-繁琐工作自动化-第三版-六-

Python 自动化指南(繁琐工作自动化)第三版(六)

原文:automatetheboringstuff.com/

译者:飞龙

协议:CC BY-NC-SA 4.0

10 读取和写入文件

原文:automatetheboringstuff.com/3e/chapter10.html

变量是存储程序运行期间数据的好方法,但如果你想使数据在程序完成后仍然持续存在,你需要将其保存到文件中。你可以将文件的内容视为一个单独的字符串值,其大小可能达到数吉字节。在本章中,你将学习如何使用 Python 在硬盘上创建、读取和保存文件。

文件和文件路径

文件有两个关键属性:一个 文件名(通常写为一个单词)和一个 路径。路径指定了文件在计算机上的位置。例如,在我的 Windows 笔记本电脑上有一个名为 project.docx 的文件,其路径为 C:\Users\Al\Documents。文件名中最后一个点之后的部分称为文件的 扩展名,它告诉您文件的类型。文件名 project.docx 是一个 Word 文档,而 UsersAlDocuments 都是指 文件夹(也称为 目录)。文件夹可以包含文件和其他文件夹(称为 子文件夹)。例如,project.docxDocuments 文件夹中,该文件夹位于 Al 文件夹内,而 Al 文件夹又位于 Users 文件夹内。图 10-1 展示了这种文件夹组织结构。

展示一系列嵌套文件夹的图表。在名为“C:\”的文件夹中是“Users”文件夹,在“Users”文件夹中是“Al”文件夹。在“Al”文件夹中是“Documents”文件夹,在“Documents”文件夹中是一个文件,名为 project.docx。

图 10-1:文件夹层次结构中的文件

*C:* 路径部分是 根文件夹,它包含所有其他文件夹。在 Windows 上,根文件夹命名为 C:*,也称为 C: 驱动器。在 macOS 和 Linux 上,根文件夹是 /。在这本书中,我将使用 Windows 风格的根文件夹,C:*。如果你在 macOS 或 Linux 上输入交互式外壳示例,请输入 / 代替。

其他 ,如 DVD 驱动器或 USB 闪存驱动器,在不同的操作系统上会以不同的方式出现。在 Windows 上,它们作为新的、带有字母的根驱动器出现,例如 *D:* 或 *E:*。在 macOS 上,它们作为 /Volumes 文件夹下的新文件夹出现。在 Linux 上,它们作为 /mnt(“挂载”)文件夹下的新文件夹出现。此外,请注意,尽管在 Windows 和 macOS 上文件夹名称和文件名不区分大小写,但在 Linux 上它们是区分大小写的。

NOTE

由于你的系统可能包含与我不同的文件和文件夹,你将无法完全按照本章中的每个示例进行操作。不过,请尽量使用你电脑上存在的文件夹来尝试跟随。

标准化路径分隔符

在 Windows 上,路径使用反斜杠(\)作为文件夹名称之间的分隔符。然而,macOS 和 Linux 操作系统使用正斜杠(/*)作为它们的路径分隔符。

pathlib模块中的Path()函数处理所有操作系统,因此最佳实践是在你的 Python 代码中使用正斜杠。如果你传递路径中单个文件和文件夹名称的字符串值,Path()将返回一个使用正确路径分隔符的文件路径字符串。在交互式 shell 中输入以下内容:

>>> from pathlib import Path
>>> Path('spam', 'bacon', 'eggs')
WindowsPath('spam/bacon/eggs')
>>> str(Path('spam', 'bacon', 'eggs'))
'spam\\bacon\\eggs' 

虽然WindowsPath对象可能使用/正斜杠,但使用str()函数将其转换为字符串需要使用\反斜杠。请注意,导入pathlib的约定是运行from pathlib import Path,否则我们不得不在我们的代码中每次出现Path时都输入pathlib.Path。这不仅是一种多余的输入,而且也是不必要的。

我正在 Windows 上运行本章的交互式 shell 示例,所以Path('spam', 'bacon', 'eggs')返回了一个表示为WindowsPath('spam/bacon/eggs')WindowsPath对象。尽管 Windows 使用反斜杠,但交互式 shell 中WindowsPath的表示使用正斜杠,因为开源软件开发者传统上更喜欢 Linux 操作系统。

如果你想要获取这个路径的简单文本字符串,你可以将它传递给str()函数,在我们的例子中,它返回'spam\\bacon\\eggs'。(注意,我们使用了双反斜杠,因为我们需要用另一个反斜杠字符来转义每个反斜杠。)如果我在这台 macOS 或 Linux 系统上调用这个函数,Path()将返回一个PosixPath对象,当传递给str()时,将返回'spam/bacon/eggs'。(POSIX是一组 Unix-like 操作系统的标准。)

如果你使用Path对象,WindowsPathPosixPath通常不需要直接出现在你的源代码中。这些Path对象将被传递给本章中介绍的一些文件相关函数。例如,以下代码将来自文件名列表的名称连接到文件夹名称的末尾:

>>> from pathlib import Path
>>> my_files = ['accounts.txt', 'details.csv', 'invite.docx']
>>> for filename in my_files:
...     print(Path(r'C:\Users\Al', filename))
...
C:\Users\Al\accounts.txt
C:\Users\Al\details.csv
C:\Users\Al\invite.docx 

在 Windows 上,反斜杠用于分隔目录,因此你无法在文件名中使用它。然而,你可以在 macOS 和 Linux 的文件名中使用反斜杠。因此,虽然Path(r'spam\eggs')在 Windows 上指的是两个独立的文件夹(或文件夹spam中的文件eggs),但在 macOS 和 Linux 上,相同的命令将指的是一个名为spam\eggs的单个文件夹(或文件)。因此,通常在 Python 代码中始终使用正斜杠是一个好主意(我将在本章的其余部分这样做)。pathlib模块将确保你的代码在所有操作系统上都能正常工作。

路径连接

我们通常使用 + 操作符来添加两个整数或浮点数,例如在表达式 2 + 2 中,它评估为整数 4。但我们也可以使用 + 操作符来连接两个字符串值,例如表达式 'Hello' + 'World',它评估为字符串 'HelloWorld'。同样,我们通常用于除法的 / 操作符可以组合 Path 对象和字符串。这在您已经使用 Path() 函数创建 Path 对象之后修改它时很有用。

例如,在交互式外壳中输入以下内容:

>>> from pathlib import Path
>>> Path('spam') / 'bacon' / 'eggs'
WindowsPath('spam/bacon/eggs')
>>> Path('spam') / Path('bacon/eggs')
WindowsPath('spam/bacon/eggs')
>>> Path('spam') / Path('bacon', 'eggs')
WindowsPath('spam/bacon/eggs') 

当使用 / 操作符连接路径时,您需要记住的是,表达式中的前两个值之一必须是 Path 对象。这是因为这些表达式是从左到右评估的,/ 操作符可以用于两个 Path 对象或一个 Path 对象和一个字符串,但不能用于两个字符串。如果您尝试在交互式外壳中输入以下内容,Python 将会报错:

>>> 'spam' / 'bacon'
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'str' 

因此,整个表达式要评估为 Path 对象,第一个或第二个最左边的值必须是 Path 对象。以下是 / 操作符和 Path 对象如何评估为最终的 Path 对象的示例:

一个显示  对象值评估的图表

描述

如果您看到之前显示的 TypeError: unsupported operand type(s) for /: 'str' and 'str' 错误消息,您需要在表达式的左侧放置一个 Path 对象而不是字符串。

/ 操作符取代了较旧的 os.path.join() 函数,您可以在 docs.python.org/3/library/os.path.html#os.path.join 了解更多。

访问当前工作目录

在您的计算机上运行的每个程序都有一个 当前工作目录。任何不以根文件夹开始的文件名或路径都被假定为位于当前工作目录下。

注意

While 文件夹是目录的更现代名称,请注意,当前工作目录(或简称工作目录)是标准术语,而不是当前工作文件夹

您可以使用 Path.cwd() 函数获取当前工作目录的字符串值,并使用 os.chdir() 来更改它。在交互式外壳中输入以下内容:

>>> from pathlib import Path
>>> import os
>>> Path.cwd()
WindowsPath('C:/Users/Al/AppData/Local/Programs/Python/Python313')
>>> os.chdir('C:\\Windows\\System32')
>>> Path.cwd()
WindowsPath('C:/Windows/System32') 

在这里,当前工作目录被设置为 C:\Users\Al\AppData\Local\Programs\Python\Python313,因此文件名 project.docx 指的是 C:\Users\Al\AppData\Local\Programs\Python\Python313\project.docx。当我们更改当前工作目录到 C:\Windows\System32 时,文件名 project.docx 被解释为 C:\Windows\System32\project.docx

如果您尝试更改到不存在的目录,Python 将会显示错误:

>>> import os
>>> os.chdir('C:/ThisFolderDoesNotExist')
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
FileNotFoundError: [WinError 2] The system cannot find the file specified:
'C:/ThisFolderDoesNotExist' 

没有用于更改工作目录的 pathlib 函数。您必须使用 os.chdir()

os.getcwd() 函数是获取当前工作目录字符串的较旧方式。它在 docs.python.org/3/library/os.html#os.getcwd 中有文档说明。

访问家目录

所有用户在他们的计算机上都有一个用于自己文件的文件夹;这个文件夹被称为 家目录家文件夹。您可以通过调用 Path.home() 来获取家文件夹的 Path 对象:

>>> from pathlib import Path
>>> Path.home()
WindowsPath('C:/Users/Al') 

家目录的位置取决于您的操作系统:

  • 在 Windows 上,家目录位于 C:\Users 下。

  • 在 macOS 上,家目录位于 /Users 下。

  • 在 Linux 上,家目录通常位于 /home 下。

您的脚本几乎肯定有权限读取和写入家目录下的文件,因此这是一个放置您 Python 程序将处理的文件的理想位置。

指定绝对路径与相对路径

指定文件路径有两种方式:

  • 绝对路径,它始终以根文件夹开头(Windows 上的 *C:*,macOS 和 Linux 上的 /

  • 相对路径,它是相对于程序当前工作目录的

在 Windows 上,*C:* 是主硬盘的根目录。这种字母标记可以追溯到 20 世纪 60 年代,当时计算机有两个标记为 *A:* 和 *B:* 的软盘驱动器。在 Windows 上,USB 闪存驱动器和 DVD 驱动器分配给 *D:* 和更高的字母。使用这些驱动器之一作为根文件夹来访问该存储介质上的文件。

还有 (.) 和 点点 (..) 文件夹。这些不是真正的文件夹,而是可以在文件路径中使用的特殊名称。文件夹名称中的一个点(点)是 此文件夹 的简写。两个点(点点)表示 父文件夹

图 10-2 显示了一些示例文件夹和文件。当当前工作目录设置为 C:\bacon 时,其他文件夹和文件的相对路径设置如图所示。

一个显示嵌套文件夹和文件的图表,其中包含目录中每个级别的文件夹或文件的相对和绝对路径。

图 10-2:工作目录 C:\bacon 中文件夹和文件的相对路径描述

相对路径开头的 .* 是可选的。例如,.\spam.txt* 和 spam.txt 指的是同一个文件。

创建新文件夹

您的程序可以使用 os.makedirs() 函数创建新文件夹。在交互式外壳中输入以下内容:

>>> import os
>>> os.makedirs('C:\\delicious\\walnut\\waffles') 

这将创建不仅 C:\delicious 文件夹,而且在 C:\delicious 内部创建一个 walnut 文件夹,在 C:\delicious\walnut 内部创建一个 waffles 文件夹。也就是说,os.makedirs() 将创建任何必要的中间文件夹,以确保完整路径存在。图 10-3 显示了这种文件夹层次结构。

一个嵌套的目录结构。文件夹“C:\”包含文件夹“delicious”,它包含文件夹“walnut”,它包含文件夹“waffles。”

图 10-3:os.makedirs('C:\\delicious\\ walnut\\waffles')的结果

要从Path对象创建目录,请调用mkdir()方法。例如,以下代码将在我的计算机上的主目录下创建一个名为spam的文件夹:

>>> from pathlib import Path
>>> Path(r'C:\Users\Al\spam').mkdir() 

注意,除非你传递parents=True,否则mkdir()一次只能创建一个目录。在这种情况下,它还会创建所有必要的父文件夹。

处理绝对路径和相对路径

Path对象上调用is_absolute()方法将返回True,如果它表示一个绝对路径,或者返回False,如果它表示一个相对路径。例如,在交互式 shell 中输入以下内容,使用你自己的文件和文件夹而不是这里列出的确切文件和文件夹:

>>> from pathlib import Path
>>> Path.cwd()
WindowsPath('C:/Users/Al/AppData/Local/Programs/Python/Python312')
>>> Path.cwd().is_absolute()
True
>>> Path('spam/bacon/eggs').is_absolute()
False 

要从相对路径获取绝对路径,你可以在相对的Path对象前加上Path.cwd()。毕竟,当我们说“相对路径”时,我们几乎总是指相对于当前工作目录的路径。absolute()方法也会返回这个Path对象。在交互式 shell 中输入以下内容:

>>> from pathlib import Path
>>> Path('my/relative/path')
WindowsPath('my/relative/path')
>>> Path.cwd() / Path('my/relative/path')
WindowsPath('C:/Users/Al/Desktop/my/relative/path')
>>> Path('my/relative/path').absolute()
WindowsPath('C:/Users/Al/Desktop/my/relative/path') 

如果你的相对路径不是相对于当前工作目录的另一个路径,请将Path.cwd()替换为那个其他路径。以下示例使用主目录而不是当前工作目录来获取绝对路径:

>>> from pathlib import Path
>>> Path('my/relative/path')
WindowsPath('my/relative/path')
>>> Path.home() / Path('my/relative/path')
WindowsPath('C:/Users/Al/my/relative/path') 

Path对象用于表示相对路径和绝对路径。唯一的区别是Path对象是否以根文件夹开头。

获取文件路径的部分

给定一个Path对象,你可以使用几个Path对象属性提取文件路径的不同部分作为字符串。这些属性可以用于根据现有路径构建新的文件路径。图 10-4 展示了这些属性。

Windows 和 macOS 上文件路径的示意图,显示了锚点、父目录、名称、驱动器、主体和后缀。

图 10-4:Windows(顶部)和 macOS/Linux(底部)文件路径的部分描述

文件路径的部分包括以下内容:

  • anchor,即文件系统的根文件夹

  • 在 Windows 上,drive是一个单字母,通常表示物理硬盘或其他存储设备

  • parent,即包含文件的文件夹

  • 文件的name,由stem(或base name)和suffix(或extension)组成

注意,Windows 的Path对象有一个drive属性,但 macOS 和 Linux 的Path对象没有。drive属性不包括第一个反斜杠。

要从文件路径中提取每个属性,请在交互式 shell 中输入以下内容:

>>> from pathlib import Path
>>> p = Path('C:/Users/Al/spam.txt')
>>> p.anchor
'C:\\'
>>> p.parent 
WindowsPath('C:/Users/Al')
>>> p.name
'spam.txt'
>>> p.stem
'spam'
>>> p.suffix
'.txt'
>>> p.drive
'C:' 

这些属性评估为简单的字符串值,除了 parent,它评估为另一个 Path 对象。如果你想根据其分隔符拆分路径,访问 parts 属性以获取字符串值的元组:

>>> from pathlib import Path
>>> p = Path('C:/Users/Al/spam.txt')
>>> p.parts
('C:\\', 'Users', 'Al', 'spam.txt')
 >>> p.parts[3]
'spam.txt'
>>> p.parts[0:2]
('C:\\', 'Users') 

注意,尽管在 Path() 调用中使用的字符串包含正斜杠,但 parts 在 Windows 上使用一个锚点,该锚点具有适当的反斜杠:'C:\\'(或作为未转义反斜杠的原始字符串 r'C:\')。

parents 属性(与 parent 属性不同)评估为 Path 对象的祖先文件夹,具有整数索引:

>>> from pathlib import Path
>>> Path.cwd()
WindowsPath('C:/Users/Al/Desktop')
>>> Path.cwd().parents[0]
WindowsPath('C:/Users/Al')
>>> Path.cwd().parents[1]
WindowsPath('C:/Users')
>>> Path.cwd().parents[2]
WindowsPath('C:/') 

如果你继续跟踪父文件夹,最终你会到达根文件夹。

查找文件大小和时间戳

一旦你有处理文件路径的方法,你就可以开始收集有关特定文件和文件夹的信息。stat() 方法返回一个包含文件大小和时间戳信息的 stat_result 对象。

例如,在交互式 shell 中输入以下内容,以了解 Windows 上的 calc.exe 程序文件:

>>> from pathlib import Path
>>> calc_file = Path('C:/Windows/System32/calc.exe')
>>> calc_file.stat()
os.stat_result(st_mode=33279, st_ino=562949956525418, st_dev=3739257218,
st_nlink=2, st_uid=0, st_gid=0, st_size=27648, st_atime=1678984560,
st_mtime=1575709787, st_ctime=1575709787)
>>> calc_file.stat().st_size
27648
>>> calc_file.stat().st_mtime
1712627129.0906117
>>> import time
>>> time.asctime(time.localtime(calc_file.stat().st_mtime))
'Mon Apr  8 20:45:29 2024' 

stat() 方法返回的 stat_result 对象的 st_size 属性是文件的大小(以字节为单位)。你可以通过除以 10241024 ** 21024 ** 3 来分别得到 KB、MB 或 GB 的大小。

st_mtime 是“最后修改”时间戳,这在确定例如 .docx Word 文件最后一次更改的时间时可能很有用。这个时间戳是 Unix 纪元时间,即自 1970 年 1 月 1 日以来的秒数。time 模块(在第十九章中解释)有将这个数字转换为人类可读形式的函数。

stat_result 对象有几个有用的属性:

st_size 文件的大小(以字节为单位)。

st_mtime 文件最后更改时的“最后修改”时间戳。

st_ctime “创建”时间戳。在 Windows 上,这标识文件创建的时间。在 macOS 和 Linux 上,这标识文件元数据(如名称)最后一次更改的时间。

st_atime “最后访问”时间戳,即文件最后一次被读取的时间。

请记住,修改、创建和访问时间戳可以手动更改,并且不能保证其准确性。

使用 glob patterns 查找文件

*? 字符可以用于匹配称为 glob patterns 的文件夹名和文件名。Glob patterns 类似于简化的正则表达式语言:* 字符匹配任何文本,而 ? 字符匹配恰好一个字符。例如,看看这些 glob patterns:

'*.txt' 匹配所有以 .txt 结尾的文件。

'project?.txt' 匹配 'project1.txt''project2.txt''projectX.txt'

'*project?.*' 匹配 'catproject5.txt''secret_project7.docx'

'*' 匹配所有文件名。

文件夹的 Path 对象有一个 glob() 方法,用于列出文件夹中与 glob 模式匹配的任何内容。glob() 方法返回一个生成器对象(该主题超出了本书的范围),你需要将其传递给 list() 以在交互式外壳中轻松查看:

>>> from pathlib import Path
>>> p = Path('C:/Users/Al/Desktop')
>>> p.glob('*')
<generator object Path.glob at 0x000002A6E389DED0>
>>> list(p.glob('*'))
[WindowsPath('C:/Users/Al/Desktop/1.png'), WindowsPath('C:/Users/Al/
Desktop/22-ap.pdf'), WindowsPath('C:/Users/Al/Desktop/cat.jpg'),
WindowsPath('C:/Users/Al/Desktop/zzz.txt')] 

你也可以在 for 循环中使用 glob() 返回的生成器对象:

>>> from pathlib import Path
>>> for name in Path('C:/Users/Al/Desktop').glob('*'):
>>>     print(name)
C:\Users\Al\Desktop\1.png
C:\Users\Al\Desktop\22-ap.pdf
C:\Users\Al\Desktop\cat.jpg
C:\Users\Al\Desktop\zzz.txt 

如果你想要对文件夹中的每个文件执行操作,例如将其复制到备份文件夹或重命名,glob('*') 方法调用可以为你提供这些文件和文件夹的 Path 对象列表。请注意,glob 模式也常用于命令行命令,如 lsdir。第十二章将更详细地讨论命令行。

检查路径有效性

如果你提供给 Python 函数一个不存在的路径,许多函数会因错误而崩溃。幸运的是,Path 对象有方法来检查给定的路径是否存在以及它是一个文件还是一个文件夹。假设一个变量 p 包含一个 Path 对象,你可以期待以下结果:

  • 调用 p.exists() 如果路径存在则返回 True,如果不存在则返回 False

  • 调用 p.is_file() 如果路径存在且是文件,则返回 True,否则返回 False

  • 调用 p.is_dir() 如果路径存在且是目录,则返回 True,否则返回 False

在我的计算机上,当我尝试在交互式外壳中使用这些方法时,我得到以下结果:

>>> from pathlib import Path
>>> win_dir = Path('C:/Windows')
>>> not_exists_dir = Path('C:/This/Folder/Does/Not/Exist')
>>> calc_file_path = Path('C:/Windows
/System32/calc.exe')
>>> win_dir.exists()
True
>>> win_dir.is_dir()
True
>>> not_exists_dir.exists()
False
>>> calc_file_path.is_file()
True
>>> calc_file_path.is_dir()
False 

你可以通过 exists() 方法检查计算机上是否当前连接了 DVD 或闪存盘。例如,如果我想检查我的 Windows 计算机上名为 *D:* 的闪存盘,我可以使用以下方法:

>>> from pathlib import Path
>>> d_drive = Path('D:/')
>>> d_drive.exists()
False 

哎呀!看起来我忘记插入我的闪存盘了。

较旧的 os.path 模块可以通过 os.path.exists(path)os.path.isfile(path)os.path.isdir(path) 函数完成相同任务,这些函数的行为与它们的 Path 函数类似。从 Python 3.6 开始,这些函数可以接受 Path 对象以及文件路径的字符串。

文件读取和写入过程

一旦你熟悉了文件夹和相对路径的工作方式,你将能够指定读取和写入文件的地点。下一几节中介绍的功能适用于纯文本文件。纯文本文件 只包含基本的文本字符,不包含字体、大小或颜色信息。具有 .txt 扩展名的文本文件或具有 .py 扩展名的 Python 脚本文件是纯文本文件的例子。你可以用 Windows 记事本或 macOS TextEdit 应用程序打开这些文件,你的程序可以轻松读取其内容,然后将其作为普通字符串值处理。

二进制文件 是所有其他类型的文件,例如文档处理文件、PDF、图片、电子表格和可执行程序。如果你在记事本或 TextEdit 中打开一个二进制文件,它看起来就像混乱的胡言乱语,就像图 10-5 所示的那样。

一个包含无法理解字符和源代码的记事本窗口

图 10-5:在记事本中打开的 Windows calc.exe 程序

因为我们必须以自己的方式处理每种类型的二进制文件,所以这本书不会直接讨论读取和写入原始二进制文件。幸运的是,许多模块使处理二进制文件变得更容易,你将在本章后面探索其中之一,即shelve模块。

pathlib模块的read_text()方法返回文本文件的完整内容作为字符串。它的write_text()方法使用传递给它的字符串创建一个新的文本文件(或覆盖现有的一个)。在交互式 shell 中输入以下内容:

>>> from pathlib import Path
>>> p = Path('spam.txt')
>>> p.write_text('Hello, world!')
13
>>> p.read_text()
'Hello, world!' 

这些方法调用创建了一个名为spam.txt的文件,内容为'Hello, world!'write_text()返回的13表示向文件中写入 13 个字符。(你通常可以忽略这个返回值。)read_text()调用读取并返回新文件的内容作为字符串:'Hello, world!'

请记住,这些Path对象方法仅允许与文件进行基本交互。将文件写入的更常见方式是使用open()函数和文件对象。在 Python 中读取或写入文件有三个步骤:

  1. 调用open()函数以返回一个File对象。

  2. File对象上调用read()write()方法。

  3. 通过在File对象上调用close()方法来关闭文件。

我们将在以下部分中介绍这些步骤。

注意,当你开始处理文件时,你可能发现能够快速查看它们的扩展名(如.txt, .pdf, .jpg等)很有帮助。Windows 和 macOS 默认可能隐藏文件扩展名,将spam.txt显示为简单的spam。要显示扩展名,请打开文件资源管理器(在 Windows 上)或 Finder(在 macOS 上)的设置,并查找一个类似“显示所有文件名扩展名”或“隐藏已知文件类型的扩展名”的复选框。(此设置的精确位置和措辞取决于你的操作系统版本。)

打开文件

要使用open()函数打开文件,传递一个表示你想要打开的文件的字符串路径。这可以是绝对路径或相对路径。open()函数返回一个File对象。

尝试使用记事本或 TextEdit 创建一个名为hello.txt的文本文件。将Hello, world!作为此文本文件的内容,并将其保存在你的用户主目录中。然后,在交互式 shell 中输入以下内容:

>>> from pathlib import Path
>>> hello_file = open(Path.home() / 'hello.txt', encoding='UTF-8') 

open() 函数将以“读取纯文本”模式打开文件,或简称为“读取模式”。当文件以读取模式打开时,Python 允许你读取文件的数据,但不能以任何方式写入或修改它。读取模式是 Python 中打开文件的默认模式。但如果你不想依赖 Python 的默认设置,你可以通过将字符串值 'r' 作为 open() 的第二个参数显式指定模式。例如,open('/Users/Al/hello.txt', 'r')open('/Users/Al/hello.txt') 做的是同样的事情。

encoding 参数指定在将文件中的字节转换为 Python 文本字符串时使用哪种编码。正确的编码几乎总是 'utf-8',这也是 macOS 和 Linux 上使用的默认编码。然而,Windows 使用 'cp1252' 作为其默认编码(也称为 扩展 ASCII)。这可能导致在 Windows 上尝试读取具有非英语字符的某些 UTF-8 编码文本文件时出现问题,因此,在以纯文本读取、写入或追加模式打开文件时传递 encoding='utf-8' 是一个好习惯。二进制读取、写入和追加模式不使用 encoding 参数,所以在这种情况下你可以省略它。

open() 调用返回一个 File 对象。File 对象代表你电脑上的文件;它只是 Python 中另一种类型的值,就像你已熟悉的列表和字典一样。在前面的例子中,你将 File 对象存储在变量 hello_file 中。现在,无论何时你想从文件中读取或写入,你都可以通过在 hello_file 中的 File 对象上调用方法来实现。

读取文件内容

现在你有了 File 对象,你可以开始读取它。如果你想将整个文件内容作为字符串值读取,请使用 File 对象的 read() 方法。让我们继续使用你存储在 hello_file 中的 hello.txt File 对象。在交互式 shell 中输入以下内容:

>>> hello_content = hello_file.read()
>>> hello_content
'Hello, world!' 

你可以将文件内容视为单个大字符串值;read() 方法只是返回存储在文件中的字符串。

或者,你可以使用 readlines() 方法从文件中获取字符串值的列表,每行一个文本。例如,在 hello.txt 所在的目录中创建一个名为 sonnet29.txt 的文件,并在其中放置以下文本:

When, in disgrace with fortune and men's eyes,
I all alone beweep my outcast state,
And trouble deaf heaven with my bootless cries,
And look upon myself and curse my fate, 

确保用换行符分隔四行。然后,在交互式 shell 中输入以下内容:

>>> sonnet_file = open(Path.home() / 'sonnet29.txt', encoding='UTF-8')
>>> sonnet_file.readlines()
["When, in disgrace with fortune and men's eyes,\n", 'I all alone beweep
my outcast state,\n', 'And trouble deaf heaven with my bootless cries,\n',
'And look upon myself and curse my fate,'] 

注意,除了文件的最后一行外,每个字符串值都以换行符 \n 结尾。与单个大字符串值相比,字符串列表通常更容易处理。

写入文件

Python 允许你将内容写入文件,就像 print() 函数将字符串写入屏幕一样。但是,你不能在以读取模式打开的文件中写入。相反,你需要以“写入纯文本”模式或“追加纯文本”模式打开它,简称为“写入模式”和“追加模式”。

写入模式将覆盖现有文件,这类似于用新值覆盖变量的值。将 'w' 作为 open() 的第二个参数传递以以写入模式打开文件。另一方面,追加模式将在现有文件的末尾追加文本。您可以将其视为将值追加到变量的列表中,而不是完全覆盖变量。将 'a' 作为 open() 的第二个参数传递以以追加模式打开文件。

如果传递给 open() 的文件名不存在,写入模式和追加模式都将创建一个新的空白文件。在读取或写入文件后,在再次打开文件之前调用 close() 方法。

让我们将这些概念结合起来。在交互式外壳中输入以下内容:

>>> bacon_file = open('bacon.txt', 'w', encoding='UTF-8') 
>>> bacon_file.write('Hello, world!\n')
14
>>> bacon_file.close()
>>> bacon_file = open('bacon.txt', 'a', encoding='UTF-8') 
>>> bacon_file.write('Bacon is not a vegetable.')
25
>>> bacon_file.close()
>>> bacon_file = open('bacon.txt', encoding='UTF-8')
>>> content = bacon_file.read()
>>> bacon_file.close()
>>> print(content)
Hello, world!
Bacon is not a vegetable. 

首先,我们以写入模式打开 bacon.txt。由于尚不存在 bacon.txt 文件,Python 将创建一个。在打开的文件上调用 write() 并将字符串参数 'Hello, world!\n' 传递给 write(),将字符串写入文件并返回写入的字符数,包括换行符。然后,我们关闭文件。

要向现有文件内容中添加文本而不是替换我们刚刚写入的字符串,我们以追加模式打开文件。我们将 'Bacon is not a vegetable.' 写入文件并关闭它。最后,为了将文件内容打印到屏幕上,我们以默认的读取模式打开文件,调用 read(),将结果 File 对象存储在 content 中,关闭文件,并打印 content

注意,write() 方法不会像 print() 函数那样自动在字符串末尾添加换行符。您必须自己添加这个字符。

您也可以将 Path 对象传递给 open() 函数,而不是作为字符串的文件名。

使用 with 语句

您的程序对每个调用 open() 的文件都需要调用 close(),但您可能忘记包含 close() 函数,或者您的程序可能在某些情况下跳过 close() 调用。

Python 的 with 语句使自动关闭文件变得更容易。with 语句创建了一个名为 context manager 的东西,Python 使用它来管理资源。这些资源,如文件、网络连接或内存段,在分配和稍后释放以供其他程序使用的过程中,通常有设置和拆除步骤。(然而,大多数时候,您会遇到用于打开文件的 with 语句。)

with 语句添加了一块代码,它首先分配资源,然后在程序执行离开该块时释放资源,这可能是由于 return 语句、未处理的异常被引发或其他原因。

这里是写入和读取文件内容的典型代码:

file_obj = open('data.txt', 'w', encoding='utf-8')
file_obj.write('Hello, world!')
file_obj.close()
file_obj = open('data.txt', encoding='utf-8')
content = file_obj.read()
file_obj.close() 

这里是使用 with 语句的等效代码:

with open('data.txt', 'w', encoding='UTF-8') as file_obj:
    file_obj.write('Hello, world!')
with open('data.txt', encoding='UTF-8') as file_obj:
    content = file_obj.read() 

with 语句示例中,请注意,根本没有任何 close() 调用,因为 with 语句在程序执行离开块时自动调用它。with 语句知道这样做是因为它从 open() 函数获得的上下文管理器。创建自己的上下文管理器超出了本书的范围,但您可以从在线文档 docs.python.org/3/reference/datamodel.html#context-managers 或 Julien Danjou 的书籍 Serious Python(No Starch Press,2018 年)中了解它们。

使用 shelve 模块保存变量

您可以使用 shelve 模块在 Python 程序中保存变量到二进制存储文件。这可以让您的程序在下次运行时将数据恢复到变量中。您可以使用此技术为程序添加保存和打开功能;例如,如果您运行了一个程序并输入了一些配置设置,您可以将这些设置保存到存储文件中,然后让程序在下次运行时加载这些设置。

要练习使用 shelve,请在交互式外壳中输入以下内容:

>>> import shelve
>>> shelf_file = shelve.open('mydata')

>>> shelf_file['cats'] = ['Zophie', 'Pooka', 'Simon']
>>> shelf_file.close() 

要使用 shelve 模块读取和写入数据,您首先导入 shelve。接下来,调用 shelve.open() 并传递一个文件名,然后将返回的存储值存储在一个变量中。您可以像字典一样更改存储值。完成操作后,对存储值调用 close()。在这里,我们的存储值存储在 shelf_file 中。我们创建一个列表 cats 并将 shelf_file['cats'] = ['Zophie', 'Pooka', 'Simon'] 写入 shelf_file,将列表存储为与键 'cats' 关联的值(就像在字典中一样)。然后,我们对 shelf_file 调用 close()

在 Windows 上运行前面的代码后,您应该看到当前工作目录中有三个新文件:mydata.bakmydata.datmydata.dir。在 macOS 上,您应该只看到一个 mydata.db 文件,而 Linux 有一个单独的 mydata 文件。这些二进制文件包含您存储在存储中的数据。这些二进制文件的格式并不重要;您只需要知道 shelve 模块做什么,而不是它如何做。该模块让您免于担心如何将程序数据存储到文件中。

您的程序可以使用 shelve 模块来稍后重新打开并检索这些存储文件中的数据。存储值一旦打开,就不需要以读取或写入模式打开;它们允许打开后进行读写。在交互式外壳中输入以下内容:

>>> shelf_file = shelve.open('mydata')
>>> type(shelf_file) 
<class 'shelve.DbfilenameShelf'>
>>> shelf_file['cats']
['Zophie', 'Pooka', 'Simon']
>>> shelf_file.close() 

在这里,我们打开存储文件以检查它们是否正确存储了数据。输入 shelf_file['cats'] 返回我们之前创建的相同列表。现在我们知道文件已正确存储列表,我们调用 close()

就像字典一样,shelf 值有 keys()values() 方法,这些方法将返回类似列表的键和值。因为这些返回值不是真正的列表,所以你应该将它们传递给 list() 函数以获取列表形式。在交互式外壳中输入以下内容:

>>> shelf_file = shelve.open('mydata')
>>> list(shelf_file.keys())
['cats']
>>> list(shelf_file.values())
[['Zophie', 'Pooka', 'Simon']]
>>> shelf_file.close() 

纯文本对于创建你将在文本编辑器(如记事本或 TextEdit)中读取的文件很有用,但如果你想在 Python 程序中保存数据,请使用 shelve 模块。

项目 4:生成随机测验文件

假设你是一位有 35 名学生的地理老师,你想对美国州首府进行一次突击测验。唉,你的班级里有一些坏学生,你无法信任学生不会作弊。你希望随机排序问题,使每个测验都是独一无二的,这样任何人都不可能从别人那里抄袭答案。当然,手动做这件事会非常耗时且无聊。幸运的是,你懂一些 Python。

下面是程序执行的操作:

  • 创建 35 个不同的测验

  • 为每个测验创建 50 个多项选择题,并随机排序

  • 为每个问题提供正确答案和三个随机错误的答案,并随机排序

  • 将测验写入 35 个文本文件

  • 将答案键写入 35 个文本文件

这意味着代码需要执行以下操作:

  • 将州及其首府存储在字典中。

  • 对测验和答案键文本文件调用 open()write()close()

  • 使用 random.shuffle() 随机排序问题和多项选择题的顺序。

让我们开始吧。

第一步:将测验数据存储在字典中

第一步是创建一个骨架脚本,并用你的测验数据填充它。创建一个名为 randomQuizGenerator.py 的文件,并使其看起来如下所示:

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

import random # ❶

# The quiz data. Keys are states and values are their capitals.
capitals = {'Alabama': 'Montgomery', 'Alaska': 'Juneau', 'Arizona': # ❷
'Phoenix', 'Arkansas': 'Little Rock', 'California': 'Sacramento', 'Colorado':
'Denver', 'Connecticut': 'Hartford', 'Delaware': 'Dover', 'Florida':
'Tallahassee', 'Georgia': 'Atlanta', 'Hawaii': 'Honolulu', 'Idaho': 'Boise',
'Illinois': 'Springfield', 'Indiana': 'Indianapolis', 'Iowa': 'Des Moines',
'Kansas': 'Topeka', 'Kentucky': 'Frankfort', 'Louisiana': 'Baton Rouge',
'Maine': 'Augusta', 'Maryland': 'Annapolis', 'Massachusetts': 'Boston',
'Michigan': 'Lansing', 'Minnesota': 'Saint Paul', 'Mississippi': 'Jackson',
'Missouri': 'Jefferson City', 'Montana': 'Helena', 'Nebraska': 'Lincoln',
'Nevada': 'Carson City', 'New Hampshire': 'Concord', 'New Jersey': 'Trenton',
'New Mexico': 'Santa Fe', 'New York': 'Albany', 'North Carolina': 'Raleigh',
'North Dakota': 'Bismarck', 'Ohio': 'Columbus', 'Oklahoma': 'Oklahoma City',
'Oregon': 'Salem', 'Pennsylvania': 'Harrisburg', 'Rhode Island': 'Providence',
'South Carolina': 'Columbia', 'South Dakota': 'Pierre', 'Tennessee':
'Nashville', 'Texas': 'Austin', 'Utah': 'Salt Lake City', 'Vermont':
'Montpelier', 'Virginia': 'Richmond', 'Washington': 'Olympia', 'West
Virginia':'Charleston', 'Wisconsin': 'Madison', 'Wyoming': 'Cheyenne'}

# Generate 35 quiz files.
for quiz_num in range(35): # ❸
    # TODO: Create the quiz and answer key files.

    # TODO: Write out the header for the quiz.

    # TODO: Shuffle the order of the states.

    # TODO: Loop through all 50 states, making a question for each. 

因为这个程序将随机排序问题和答案,你需要导入 random 模块❶以使用其功能。capitals 变量❷包含一个字典,其中美国州作为键,首府作为值。而且因为你想要创建 35 个测验,实际生成测验和答案键文件的代码(目前用 TODO 注释标记)将放在一个循环中,该循环循环 35 次❸。(你可以更改这个数字以生成任何数量的测验文件。)

第二步:创建测验文件

现在是时候开始填写那些 TODOs 了。

循环中的代码将重复 35 次,每次对应一个测验,所以你只需要在循环中一次处理一个测验。首先,你需要创建实际的测验文件。它需要一个唯一的文件名和一些标准标题,包括学生填写姓名、日期和课程时段的地方。然后,你需要获取一个随机排序的州列表,你可以稍后用它来创建测验的问题和答案。

将以下代码行添加到 randomQuizGenerator.py

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

# --snip--

# Generate 35 quiz files.
for quiz_num in range(35):
    # Create the quiz and answer key files.
    quiz_file = open(f'capitalsquiz{quiz_num + 1}.txt', 'w', encoding='UTF-8') ❶
    answer_file = open(f'capitalsquiz_answers{quiz_num + 1}.txt', 'w', encoding='UTF-8') ❷

    # Write out the header for the quiz.
    quiz_file.write('Name:\n\nDate:\n\nPeriod:\n\n') ❸
    quiz_file.write((' ' * 20) + f'State Capitals Quiz (Form{quiz_num + 1})')
    quiz_file.write('\n\n')

    # Shuffle the order of the states.
 states = list(capitals.keys())
    random.shuffle(states) ❹

    # TODO: Loop through all 50 states, making a question for each. 

试题将使用文件名 capitalsquiz.txt,其中 是由 quiz_num 循环计数器生成的唯一数字。我们将 capitalsquiz.txt 的答案键存储在名为 capitalsquiz_answers.txt 的文本文件中。在循环的每次迭代中,代码将使用唯一数字替换这些文件名中的 {quiz _num + 1} 占位符。例如,它将第一个试题和答案键命名为 capitalsquiz1.txtcapitalsquiz _answers1.txt。我们通过调用 open() 函数在 ❶ 和 ❷ 处创建这些文件,并将 'w' 作为第二个参数传递以以写入模式打开它们。

在 ❸ 处的 write() 语句创建一个供学生填写的试题标题。最后,我们使用 random.shuffle() 函数 ❹ 生成一个随机的美国州列表,该函数随机重新排列传递给它的任何列表中的值。

第 3 步:创建答案选项

现在,你需要使用另一个 for 循环为每个问题生成答案选项 A 到 D。稍后,第三个嵌套的 for 循环将把这些多项选择题写入文件。让你的代码看起来像以下这样:

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

# --snip--

 # Loop through all 50 states, making a question for each.
 for num in range(50):

        # Get right and wrong answers.
 correct_answer = capitals[states[num]]
 wrong_answers = list(capitals.values())
 del wrong_answers[wrong_answers.index(correct_answer)]
 wrong_answers = random.sample(wrong_answers, 3)
 answer_options = wrong_answers + [correct_answer]
 random.shuffle(answer_options)

        # TODO: Write the question and answer options to the quiz file.

        # TODO: Write the answer key to a file. 

正确答案很容易创建;它已经作为 capitals 字典中的值存储。这个循环将遍历打乱顺序的 states 列表中的州,在 capitals 中找到每个州,并将该州的对应首都存储在 correct_answer 中。

创建可能的错误答案列表比较复杂。你可以通过复制 capitals 字典中的值,删除正确答案,并从该列表中选择三个随机值来获得它。random.sample() 函数使执行此选择变得容易。它的第一个参数是你想要从中选择的列表,第二个参数是你想要选择的值的数量。完整的答案选项列表将结合这三个错误答案和正确答案。最后,我们随机化答案,以确保正确答案不总是选择 D。

第 4 步:将内容写入文件

剩下的就是将问题写入试题文件,答案写入答案键文件。让你的代码看起来像以下这样:

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

# --snip--

    # Loop through all 50 states, making a question for each.
    for num in range(50):
        # --snip--

        # Write the question and the answer options to the quiz file.
        quiz_file.write(f'{num + 1}. Capital of {states[num]}:\n')
          for i in range(4): # ❶
              for i in range(4): # ❶
") 
        quiz_file.write('\n')

        # Write the answer key to a file.
          for i in range(4): # ❶
    quiz_file.close()
    answer_file.close() 

一个 for 循环遍历整数 03,将 answer_options 列表中的答案选项写入文件 ❶。在 ❷ 处的 'ABCD'[i] 表达式将字符串 'ABCD' 作为数组处理,并在循环的每次迭代中评估为 'A''B''C''D'

在循环的最后一行,表达式 answer_options.index(correct_answer) ❸ 将找到随机排序的答案选项中正确答案的整数索引,导致正确答案的字母被写入答案键文件。

运行程序后,你的 capitalsquiz1.txt 文件应该看起来像这样。当然,你的问题和答案选项将取决于你的 random.shuffle() 调用的结果:

Name:

Date:

Period:

                    State Capitals Quiz (Form 1)

1\. What is the capital of West Virginia?
    A. Hartford
    B. Santa Fe
    C. Harrisburg
    D. Charleston

2\. What is the capital of Colorado?
    A. Raleigh
    B. Harrisburg
    C. Denver
    D. Lincoln

# --snip-- 

相应的 capitalsquiz_answers1.txt 文本文件将看起来像这样:

1\. D
2\. C

# --snip-- 

手动随机排序问题集和相应的答案键需要花费数小时,但只要有一点编程知识,你就可以自动化这项无聊的任务,不仅限于州首府测验,还可以用于任何多项选择题。

摘要

操作系统将文件组织到文件夹(也称为目录)中,并使用路径来描述它们的位置。运行在计算机上的每个程序都有一个当前工作目录,这使得你可以指定相对于当前位置的文件路径,而不是输入完整的(或绝对)路径。pathlibos.path模块有许多用于操作文件路径的函数。

你的程序也可以直接与文本文件的内容进行交互。open()函数可以打开这些文件,以一个大的字符串(使用read()方法)或字符串列表(使用readlines()方法)的形式读取其内容。open()函数还可以以写入或追加模式打开文件,分别用于创建新的文本文件或向现有文本文件添加内容。

在前面的章节中,你使用剪贴板作为将大量文本输入程序的一种方式,而不是直接输入。现在你可以让程序从硬盘读取文件,这是一个很大的改进,因为文件比剪贴板更稳定。

在下一章中,你将学习如何通过复制、删除、重命名、移动等方式处理文件本身。

练习问题

1.  相对路径相对于什么而言?

2.  绝对路径从哪里开始?

3.  在 Windows 上,Path('C:/Users') / 'Al'评估为什么?

4.  在 Windows 上,'C:/Users' / 'Al'评估为什么?

5.  os.getcwd()os.chdir()函数的作用是什么?

6.  ...文件夹是什么?

7.  在C:\bacon\eggs\spam.txt中,哪部分是目录名,哪部分是基本名?

8.  你可以向open()函数传递哪三个“模式”参数来处理纯文本文件?

9.  如果以写入模式打开现有文件会发生什么?

10.  read()readlines()方法之间的区别是什么?

11.  货架值类似于哪种数据结构?

练习程序

为了练习,设计和编写以下程序。

Mad Libs

创建一个 Mad Libs 程序,该程序读取文本文件,并允许用户在文本文件中任何出现单词ADJECTIVENOUNADVERBVERB的地方添加自己的文本。例如,一个文本文件可能看起来像这样:

The ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was
unaffected by these events. 

程序会找到这些出现并提示用户替换它们:

Enter an adjective:
silly
Enter a noun:
chandelier
Enter a verb:
screamed
Enter a noun:
pickup truck

然后它会创建以下文本文件:

The silly panda walked to the chandelier and then screamed. A nearby
pickup truck was unaffected by these events. 

程序应将结果打印到屏幕上,并保存到新的文本文件中。

正则表达式搜索

编写一个程序,打开文件夹中的所有*.txt 文件,并搜索任何与用户提供的正则表达式匹配的行,然后将结果打印到屏幕上。

文件和文件路径

文件有两个关键属性:一个 文件名(通常写为一个单词)和一个 路径。路径指定了文件在计算机上的位置。例如,在我的 Windows 笔记本电脑上有一个名为 project.docx 的文件,位于路径 C:\Users\Al\Documents 中。文件名中最后一个点之后的部分称为文件的 扩展名,它告诉了你文件的类型。文件名 project.docx 是一个 Word 文档,而 UsersAlDocuments 都是指 文件夹(也称为 目录)。文件夹可以包含文件和其他文件夹(称为 子文件夹)。例如,project.docx 位于 Documents 文件夹中,该文件夹位于 Al 文件夹内,而 Al 文件夹又位于 Users 文件夹内。图 10-1 展示了这种文件夹组织结构。

展示一系列嵌套文件夹的图表。在名为“C:\”的文件夹中是“Users”文件夹,在“Users”文件夹中是“Al”文件夹。在“Al”文件夹中是“Documents”文件夹,在“Documents”文件夹中是一个文件,project.docx。

图 10-1:文件夹层次结构中的文件

路径中的 *C:* 部分是 根文件夹,其中包含所有其他文件夹。在 Windows 上,根文件夹命名为 C:*,也称为 C: 驱动器。在 macOS 和 Linux 上,根文件夹是 /。在这本书中,我将使用 Windows 风格的根文件夹,C:*。如果你在 macOS 或 Linux 的交互式外壳中输入示例,请输入 / 代替。

附加的 ,如 DVD 驱动器或 USB 闪存驱动器,在不同的操作系统上显示方式不同。在 Windows 上,它们作为新的、带字母的根驱动器出现,例如 *D:* 或 *E:*。在 macOS 上,它们作为 /Volumes 文件夹下的新文件夹出现。在 Linux 上,它们作为 /mnt(“挂载”)文件夹下的新文件夹出现。此外,请注意,尽管在 Windows 和 macOS 上文件夹名称和文件名不区分大小写,但在 Linux 上是区分大小写的。

注意

由于你的系统可能包含与我不同的文件和文件夹,你可能无法完全按照本章中的每个示例进行操作。不过,请尽量使用你电脑上存在的文件夹来尝试跟随。

标准化路径分隔符

在 Windows 上,路径使用反斜杠 (\) 作为文件夹名称之间的分隔符。然而,macOS 和 Linux 操作系统使用正斜杠 (/) 作为它们的路径分隔符。

pathlib 模块中的 Path() 函数处理所有操作系统,因此最佳实践是在你的 Python 代码中使用正斜杠。如果你传递路径中单个文件和文件夹名称的字符串值,Path() 将返回一个使用正确路径分隔符的文件路径字符串。在交互式外壳中输入以下内容:

>>> from pathlib import Path
>>> Path('spam', 'bacon', 'eggs')
WindowsPath('spam/bacon/eggs')
>>> str(Path('spam', 'bacon', 'eggs'))
'spam\\bacon\\eggs' 

虽然WindowsPath对象可能使用/正斜杠,但使用str()函数将其转换为字符串需要使用\反斜杠。请注意,导入pathlib的惯例是运行from pathlib import Path,否则我们不得不在我们的代码中每次出现Path时都输入pathlib.Path。这不仅多余的输入是多余的,而且也是不必要的。

我正在 Windows 上运行本章的交互式 shell 示例,所以Path('spam', 'bacon', 'eggs')返回了一个表示为WindowsPath('spam/bacon/eggs')WindowsPath对象。尽管 Windows 使用反斜杠,但交互式 shell 中的WindowsPath表示使用正斜杠,因为开源软件开发者历史上更喜欢 Linux 操作系统。

如果你想要获取这个路径的简单文本字符串,你可以将它传递给str()函数,在我们的例子中,它返回'spam\\bacon\\eggs'。(注意,我们使用了双反斜杠,因为我们需要用另一个反斜杠字符来转义每个反斜杠。)如果我在这台 macOS 或 Linux 系统上调用这个函数,Path()将返回一个PosixPath对象,当传递给str()函数时,将返回'spam/bacon/eggs'。(POSIX是一组 Unix-like 操作系统的标准。)

如果你与Path对象一起工作,WindowsPathPosixPath永远不需要直接出现在你的源代码中。这些Path对象将被传递给本章中介绍的一些文件相关函数。例如,以下代码将文件名列表中的名称连接到文件夹名称的末尾:

>>> from pathlib import Path
>>> my_files = ['accounts.txt', 'details.csv', 'invite.docx']
>>> for filename in my_files:
...     print(Path(r'C:\Users\Al', filename))
...
C:\Users\Al\accounts.txt
C:\Users\Al\details.csv
C:\Users\Al\invite.docx 

在 Windows 上,反斜杠用于分隔目录,因此你无法在文件名中使用它。然而,你可以在 macOS 和 Linux 的文件名中使用反斜杠。因此,虽然Path(r'spam\eggs')在 Windows 上指的是两个独立的文件夹(或文件夹spam中的文件eggs),但在 macOS 和 Linux 上,相同的命令将指的是一个名为spam\eggs的单个文件夹(或文件)。因此,通常在 Python 代码中始终使用正斜杠是一个好主意(在本章的其余部分我将这样做)。pathlib模块将确保你的代码在所有操作系统上都能正常工作。

路径连接

我们通常使用+运算符来添加两个整数或浮点数,例如在表达式2 + 2中,它评估为整数值4。但我们也可以使用+运算符来连接两个字符串值,就像表达式'Hello' + 'World',它评估为字符串值'HelloWorld'。同样,我们通常用于除法的/运算符也可以组合Path对象和字符串。这在你在使用Path()函数创建Path对象之后修改它时很有用。

例如,在交互式 shell 中输入以下内容:

>>> from pathlib import Path
>>> Path('spam') / 'bacon' / 'eggs'
WindowsPath('spam/bacon/eggs')
>>> Path('spam') / Path('bacon/eggs')
WindowsPath('spam/bacon/eggs')
>>> Path('spam') / Path('bacon', 'eggs')
WindowsPath('spam/bacon/eggs') 

当使用/运算符连接路径时,您需要记住的是,表达式中的前两个值之一必须是Path对象。这是因为这些表达式是从左到右评估的,/运算符可以用于两个Path对象或一个Path对象和一个字符串,但不能用于两个字符串。如果您尝试在交互式外壳中输入以下内容,Python 将给出错误:

>>> 'spam' / 'bacon'
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'str' 

因此,整个表达式要评估为Path对象,第一个或第二个最左边的值必须是Path对象。下面是如何使用/运算符和Path对象评估为最终的Path对象:

一个显示 Path 对象值评估的图表

描述

如果您看到之前显示的TypeError: unsupported operand type(s) for /: 'str' and 'str'错误消息,您需要在表达式的左侧放置一个Path对象而不是字符串。

/运算符替换了较旧的os.path.join()函数,您可以在docs.python.org/3/library/os.path.html#os.path.join中了解更多信息。

访问当前工作目录

在您的计算机上运行的每个程序都有一个当前工作目录。任何不以根文件夹开始的文件名或路径都被假定为位于当前工作目录下。

注意

文件夹目录的更现代名称,请注意,当前工作目录(或简称工作目录)是标准术语,而不是当前工作文件夹

您可以使用Path.cwd()函数获取当前工作目录的字符串值,并可以使用os.chdir()更改它。在交互式外壳中输入以下内容:

>>> from pathlib import Path
>>> import os
>>> Path.cwd()
WindowsPath('C:/Users/Al/AppData/Local/Programs/Python/Python313')
>>> os.chdir('C:\\Windows\\System32')
>>> Path.cwd()
WindowsPath('C:/Windows/System32') 

在这里,当前工作目录设置为C:\Users\Al\AppData\Local\Programs\Python\Python313,因此文件名project.docx指的是C:\Users\Al\AppData\Local\Programs\Python\Python313\project.docx。当我们更改当前工作目录为C:\Windows\System32时,文件名project.docx被解释为C:\Windows\System32\project.docx

如果您尝试更改到不存在的目录,Python 将显示错误:

>>> import os
>>> os.chdir('C:/ThisFolderDoesNotExist')
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
FileNotFoundError: [WinError 2] The system cannot find the file specified:
'C:/ThisFolderDoesNotExist' 

没有用于更改工作目录的pathlib函数。您必须使用os.chdir()

os.getcwd()函数是获取当前工作目录作为字符串的较旧方式。它在docs.python.org/3/library/os.html#os.getcwd中有文档说明。

访问家目录

所有用户在他们的计算机上都有一个用于他们自己文件的文件夹;这个文件夹被称为家目录家文件夹。您可以通过调用Path.home()获取家文件夹的Path对象:

>>> from pathlib import Path
>>> Path.home()
WindowsPath('C:/Users/Al') 

根据您的操作系统,家目录位于一个固定的位置:

  • 在 Windows 上,主目录位于C:\Users

  • 在 macOS 上,主目录位于/Users

  • 在 Linux 上,主目录通常位于/home

您的脚本几乎肯定有权限读取和写入您主目录下的文件,因此这是一个放置您 Python 程序将与之一起工作的文件的理想位置。

指定绝对路径与相对路径

指定文件路径有两种方式:

  • 绝对路径,始终以根文件夹开头(Windows 上的C:*,macOS 和 Linux 上的/*)

  • 相对路径,相对于程序的当前工作目录

在 Windows 上,C:*是主硬盘的根目录。这种字母标记可以追溯到 20 世纪 60 年代,当时计算机有两个标记为A:*和B:*的软盘驱动器。在 Windows 上,USB 闪存驱动器和 DVD 驱动器分配给字母D:*和更高。使用这些驱动器之一作为根文件夹以访问该存储介质上的文件。

还有点() (.) 和点点() (..) 文件夹。这些不是真正的文件夹,而是可以用于文件路径的特殊名称。文件夹名称中的一个点(点)是此文件夹的简称。两个点(点点)表示父文件夹

图 10-2 显示了某些示例文件夹和文件。当当前工作目录设置为C:\bacon时,其他文件夹和文件的相对路径设置如图所示。

显示嵌套文件夹和文件以及每个级别目录中文件夹或文件的相对和绝对路径的图表。

图 10-2:工作目录 C:\bacon 中文件夹和文件的相对路径描述

相对路径开头的.是可选的。例如,.\spam.txtspam.txt指向同一个文件。

创建新文件夹

您的程序可以使用os.makedirs()函数创建新文件夹。在交互式外壳中输入以下内容:

>>> import os
>>> os.makedirs('C:\\delicious\\walnut\\waffles') 

这将创建不仅包括C:\delicious文件夹,还包括C:\delicious中的walnut文件夹和C:\delicious\walnut中的waffles文件夹。也就是说,os.makedirs()将创建任何必要的中间文件夹,以确保完整路径存在。图 10-3 显示了文件夹的这种层次结构。

嵌套目录结构。文件夹“C:\”包含文件夹“delicious”,其中包含文件夹“walnut”,其中包含文件夹“waffles。”

图 10-3:os.makedirs('C:\\delicious\\ walnut\\waffles')的结果

要从Path对象创建目录,请调用mkdir()方法。例如,以下代码将在我的计算机上的主目录下创建一个名为spam的文件夹:

>>> from pathlib import Path
>>> Path(r'C:\Users\Al\spam').mkdir() 

注意,mkdir()一次只能创建一个目录,除非你传递parents=True,在这种情况下,它还会创建所有必要的父文件夹。

处理绝对路径和相对路径

Path对象上调用is_absolute()方法将返回True,如果它表示一个绝对路径,或者返回False,如果它表示一个相对路径。例如,在交互式外壳中输入以下内容,使用你自己的文件和文件夹而不是这里列出的确切文件和文件夹:

>>> from pathlib import Path
>>> Path.cwd()
WindowsPath('C:/Users/Al/AppData/Local/Programs/Python/Python312')
>>> Path.cwd().is_absolute()
True
>>> Path('spam/bacon/eggs').is_absolute()
False 

要从相对路径获取绝对路径,可以在相对Path对象前加上Path.cwd()。毕竟,当我们说“相对路径”时,我们几乎总是指相对于当前工作目录的路径。absolute()方法也返回此Path对象。在交互式外壳中输入以下内容:

>>> from pathlib import Path
>>> Path('my/relative/path')
WindowsPath('my/relative/path')
>>> Path.cwd() / Path('my/relative/path')
WindowsPath('C:/Users/Al/Desktop/my/relative/path')
>>> Path('my/relative/path').absolute()
WindowsPath('C:/Users/Al/Desktop/my/relative/path') 

如果你的相对路径相对于当前工作目录之外的另一个路径,请将Path.cwd()替换为那个其他路径。以下示例使用主目录而不是当前工作目录来获取绝对路径:

>>> from pathlib import Path
>>> Path('my/relative/path')
WindowsPath('my/relative/path')
>>> Path.home() / Path('my/relative/path')
WindowsPath('C:/Users/Al/my/relative/path') 

Path对象用于表示相对路径和绝对路径。唯一的区别是Path对象是否以根文件夹开头。

获取文件路径的部分

给定一个Path对象,你可以使用几个Path对象属性提取文件路径的不同部分作为字符串。这些属性对于根据现有路径构建新路径非常有用。图 10-4 展示了这些属性。

Windows 和 macOS 上文件路径的图示,显示锚点、父目录、名称、驱动器、主体和后缀。

图 10-4:Windows(顶部)和 macOS/Linux(底部)文件路径的部分描述

文件路径的部分包括以下内容:

  • 锚点,即文件系统的根文件夹

  • 在 Windows 上,驱动器,通常是一个表示物理硬盘驱动器或其他存储设备的单个字母

  • 父目录,即包含文件的文件夹

  • 文件的名称,由主体(或基本名称)和后缀(或扩展名)组成

注意,Windows Path对象有一个drive属性,但 macOS 和 Linux Path对象没有。drive属性不包括第一个反斜杠。

要从文件路径中提取每个属性,请在交互式外壳中输入以下内容:

>>> from pathlib import Path
>>> p = Path('C:/Users/Al/spam.txt')
>>> p.anchor
'C:\\'
>>> p.parent 
WindowsPath('C:/Users/Al')
>>> p.name
'spam.txt'
>>> p.stem
'spam'
>>> p.suffix
'.txt'
>>> p.drive
'C:' 

这些属性评估为简单的字符串值,除了parent,它评估为另一个Path对象。如果你想通过其分隔符拆分路径,请访问parts属性以获取一个字符串值的元组:

>>> from pathlib import Path
>>> p = Path('C:/Users/Al/spam.txt')
>>> p.parts
('C:\\', 'Users', 'Al', 'spam.txt')
 >>> p.parts[3]
'spam.txt'
>>> p.parts[0:2]
('C:\\', 'Users') 

注意,尽管在Path()调用中使用的字符串包含正斜杠,但parts在 Windows 上使用一个锚点,该锚点具有适当的反斜杠:'C:\\'(或作为原始字符串的r'C:\',其中反斜杠未转义)。

parents属性(与parent属性不同)评估为Path对象的祖先文件夹,具有整数索引:

>>> from pathlib import Path
>>> Path.cwd()
WindowsPath('C:/Users/Al/Desktop')
>>> Path.cwd().parents[0]
WindowsPath('C:/Users/Al')
>>> Path.cwd().parents[1]
WindowsPath('C:/Users')
>>> Path.cwd().parents[2]
WindowsPath('C:/') 

如果你继续跟随父文件夹,最终会到达根文件夹。

查找文件大小和时间戳

一旦你有处理文件路径的方法,你就可以开始收集关于特定文件和文件夹的信息。stat() 方法返回一个包含文件大小和时间戳信息的 stat_result 对象。

例如,在交互式 shell 中输入以下内容,以了解 Windows 上的 calc.exe 程序文件:

>>> from pathlib import Path
>>> calc_file = Path('C:/Windows/System32/calc.exe')
>>> calc_file.stat()
os.stat_result(st_mode=33279, st_ino=562949956525418, st_dev=3739257218,
st_nlink=2, st_uid=0, st_gid=0, st_size=27648, st_atime=1678984560,
st_mtime=1575709787, st_ctime=1575709787)
>>> calc_file.stat().st_size
27648
>>> calc_file.stat().st_mtime
1712627129.0906117
>>> import time
>>> time.asctime(time.localtime(calc_file.stat().st_mtime))
'Mon Apr  8 20:45:29 2024' 

stat() 方法返回的 stat_result 对象的 st_size 属性表示文件的字节数。你可以通过除以 10241024 ** 21024 ** 3 来分别得到以 KB、MB 或 GB 为单位的大小。

st_mtime 是“最后修改”时间戳,对于确定 .docx Word 文件最后一次更改的时间非常有用。这个时间戳是 Unix 纪元时间,即自 1970 年 1 月 1 日以来的秒数。time 模块(在第十九章中解释)有将这个数字转换为可读形式的函数。

stat_result 对象有几个有用的属性:

st_size:文件的字节数。

st_mtime:文件最后修改的时间戳。

st_ctime:文件的“创建”时间戳。在 Windows 上,这标识了文件创建的时间。在 macOS 和 Linux 上,这标识了文件元数据(如名称)最后更改的时间。

st_atime:文件最后访问的时间戳,即文件最后被读取的时间。

请记住,修改、创建和访问时间戳可以手动更改,并且不能保证其准确性。

使用 Glob 模式查找文件

*? 字符可以用于匹配所谓的 glob 模式 中的文件夹名和文件名。Glob 模式类似于简化的正则表达式语言:* 字符匹配任何文本,而 ? 字符匹配恰好一个字符。例如,看看这些 glob 模式:

'*.txt' 匹配所有以 *.txt 结尾的文件。

'project?.txt' 匹配 'project1.txt''project2.txt''projectX.txt'

'*project?.*' 匹配 'catproject5.txt''secret_project7.docx'

'*' 匹配所有文件名。

文件夹的 Path 对象有一个 glob() 方法,用于列出文件夹中匹配 glob 模式的任何内容。glob() 方法返回一个生成器对象(该主题超出了本书的范围),你需要将其传递给 list() 以在交互式 shell 中轻松查看:

>>> from pathlib import Path
>>> p = Path('C:/Users/Al/Desktop')
>>> p.glob('*')
<generator object Path.glob at 0x000002A6E389DED0>
>>> list(p.glob('*'))
[WindowsPath('C:/Users/Al/Desktop/1.png'), WindowsPath('C:/Users/Al/
Desktop/22-ap.pdf'), WindowsPath('C:/Users/Al/Desktop/cat.jpg'),
WindowsPath('C:/Users/Al/Desktop/zzz.txt')] 

你还可以在 for 循环中使用 glob() 方法返回的生成器对象:

>>> from pathlib import Path
>>> for name in Path('C:/Users/Al/Desktop').glob('*'):
>>>     print(name)
C:\Users\Al\Desktop\1.png
C:\Users\Al\Desktop\22-ap.pdf
C:\Users\Al\Desktop\cat.jpg
C:\Users\Al\Desktop\zzz.txt 

如果你想要对文件夹中的每个文件执行操作,例如将其复制到备份文件夹或重命名,glob('*') 方法调用可以获取这些文件和文件夹的 Path 对象列表。请注意,glob 模式也常用于命令行命令,如 lsdir。第十二章将更详细地讨论命令行。

检查路径有效性

许多 Python 函数如果提供不存在的路径,将会因错误而崩溃。幸运的是,Path 对象有方法来检查给定的路径是否存在以及它是否是文件或文件夹。假设一个变量 p 包含一个 Path 对象,您可以期待以下结果:

  • 调用 p.exists() 如果路径存在则返回 True,如果不存在则返回 False

  • 调用 p.is_file() 如果路径存在且是文件,则返回 True,否则返回 False

  • 调用 p.is_dir() 如果路径存在且是目录,则返回 True,否则返回 False

在我的电脑上,当我尝试在交互式 shell 中使用这些方法时,我得到了以下结果:

>>> from pathlib import Path
>>> win_dir = Path('C:/Windows')
>>> not_exists_dir = Path('C:/This/Folder/Does/Not/Exist')
>>> calc_file_path = Path('C:/Windows
/System32/calc.exe')
>>> win_dir.exists()
True
>>> win_dir.is_dir()
True
>>> not_exists_dir.exists()
False
>>> calc_file_path.is_file()
True
>>> calc_file_path.is_dir()
False 

您可以通过使用 exists() 方法来检查计算机上是否当前连接了 DVD 或闪存盘。例如,如果我想在我的 Windows 计算机上检查名为 *D:* 的闪存盘,我可以使用以下方法:

>>> from pathlib import Path
>>> d_drive = Path('D:/')
>>> d_drive.exists()
False 

哎呀!看起来我忘记插入我的闪存盘了。

较旧的 os.path 模块可以使用 os.path.exists(path)os.path.isfile(path)os.path.isdir(path) 函数完成相同任务,这些函数的行为与它们的 Path 函数类似。从 Python 3.6 开始,这些函数可以接受 Path 对象以及文件路径的字符串。

标准化路径分隔符

在 Windows 上,路径使用反斜杠 (\) 作为文件夹名称之间的分隔符。然而,macOS 和 Linux 操作系统使用前向斜杠 (/) 作为它们的路径分隔符。

pathlib 模块中的 Path() 函数处理所有操作系统,因此最佳实践是在您的 Python 代码中使用前向斜杠。如果您传递路径中各个文件和文件夹名称的字符串值,Path() 将返回一个使用正确路径分隔符的文件路径字符串。在交互式 shell 中输入以下内容:

>>> from pathlib import Path
>>> Path('spam', 'bacon', 'eggs')
WindowsPath('spam/bacon/eggs')
>>> str(Path('spam', 'bacon', 'eggs'))
'spam\\bacon\\eggs' 

虽然 WindowsPath 对象可能使用 / 前向斜杠,但使用 str() 函数将其转换为字符串时需要使用 \ 反斜杠。请注意,导入 pathlib 的惯例是运行 from pathlib import Path,否则我们不得不在我们的代码中到处输入 pathlib.Path。这不仅多余的输入是多余的,而且也是多余的。

我在 Windows 上运行本章的交互式 shell 示例,所以 Path('spam', 'bacon', 'eggs') 返回了一个表示连接路径的 WindowsPath 对象,表示为 WindowsPath('spam/bacon/eggs')。尽管 Windows 使用反斜杠,但交互式 shell 中的 WindowsPath 表示使用前向斜杠来显示它们,因为开源软件开发者历史上更喜欢 Linux 操作系统。

如果你想要获取此路径的简单文本字符串,你可以将其传递给 str() 函数,在我们的例子中,它返回 'spam\\bacon\\eggs'。(注意,我们使用了双反斜杠,因为我们需要用另一个反斜杠字符来转义每个反斜杠。)如果我在这台 macOS 或 Linux 机器上调用此函数,Path() 会返回一个 PosixPath 对象,当传递给 str() 时,它会返回 'spam/bacon/eggs'。(POSIX 是一组 Unix-like 操作系统的标准。)

如果你与 Path 对象一起工作,WindowsPathPosixPath 永远不需要直接出现在你的源代码中。这些 Path 对象将被传递给本章中介绍的一些文件相关函数。例如,以下代码将文件名列表中的名称连接到文件夹名称的末尾:

>>> from pathlib import Path
>>> my_files = ['accounts.txt', 'details.csv', 'invite.docx']
>>> for filename in my_files:
...     print(Path(r'C:\Users\Al', filename))
...
C:\Users\Al\accounts.txt
C:\Users\Al\details.csv
C:\Users\Al\invite.docx 

在 Windows 上,反斜杠用于分隔目录,因此你无法在文件名中使用它。然而,你可以在 macOS 和 Linux 的文件名中使用反斜杠。因此,虽然 Path(r'spam\eggs') 在 Windows 上指的是两个单独的文件夹(或文件夹 spam 中的文件 eggs),但在 macOS 和 Linux 上,相同的命令会指一个名为 spam\eggs 的单个文件夹(或文件)。因此,通常在 Python 代码中始终使用正斜杠是一个好主意(在本章的其余部分我将这样做)。pathlib 模块将确保你的代码在所有操作系统上都能正常工作。

连接路径

我们通常使用 + 操作符来添加两个整数或浮点数,例如在表达式 2 + 2 中,它评估为整数值 4。但我们也可以使用 + 操作符来连接两个字符串值,就像表达式 'Hello' + 'World',它评估为字符串值 'HelloWorld'。同样,我们通常用于除法的 / 操作符可以组合 Path 对象和字符串。这在修改使用 Path() 函数创建的 Path 对象后非常有用。

例如,将以下内容输入到交互式外壳中:

>>> from pathlib import Path
>>> Path('spam') / 'bacon' / 'eggs'
WindowsPath('spam/bacon/eggs')
>>> Path('spam') / Path('bacon/eggs')
WindowsPath('spam/bacon/eggs')
>>> Path('spam') / Path('bacon', 'eggs')
WindowsPath('spam/bacon/eggs') 

当使用 / 操作符连接路径时,你需要记住的唯一一件事是,表达式中的前两个值之一必须是 Path 对象。这是因为这些表达式是从左到右进行评估的,/ 操作符可以用于两个 Path 对象或一个 Path 对象和一个字符串,但不能用于两个字符串。如果你尝试在交互式外壳中输入以下内容,Python 会给你一个错误:

>>> 'spam' / 'bacon'
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'str' 

因此,整个表达式要评估为 Path 对象,第一个或第二个最左侧的值必须是 Path 对象。以下是 / 操作符和 Path 对象如何评估为最终的 Path 对象的示例:

一个显示 Path 对象值的评估的图表

描述

如果你看到之前显示的 TypeError: unsupported operand type(s) for /: 'str' and 'str' 错误消息,你需要将表达式左侧的字符串替换为 Path 对象。

/运算符取代了较旧的os.path.join()函数,您可以在docs.python.org/3/library/os.path.html#os.path.join了解更多信息。

访问当前工作目录

在您的计算机上运行的每个程序都有一个当前工作目录。任何不以根文件夹开始的文件名或路径都被假定为位于当前工作目录下。

注意

文件夹目录的更现代名称,请注意,当前工作目录(或简称工作目录)是标准术语,而不是当前工作文件夹

您可以使用Path.cwd()函数获取当前工作目录的字符串值,并使用os.chdir()来更改它。在交互式 shell 中输入以下内容:

>>> from pathlib import Path
>>> import os
>>> Path.cwd()
WindowsPath('C:/Users/Al/AppData/Local/Programs/Python/Python313')
>>> os.chdir('C:\\Windows\\System32')
>>> Path.cwd()
WindowsPath('C:/Windows/System32') 

在这里,当前工作目录设置为C:\Users\Al\AppData\Local\Programs\Python\Python313,因此文件名project.docx指的是C:\Users\Al\AppData\Local\Programs\Python\Python313\project.docx。当我们更改当前工作目录到C:\Windows\System32时,文件名project.docx被解释为C:\Windows\System32\project.docx

如果您尝试更改到不存在的目录,Python 将显示错误:

>>> import os
>>> os.chdir('C:/ThisFolderDoesNotExist')
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
FileNotFoundError: [WinError 2] The system cannot find the file specified:
'C:/ThisFolderDoesNotExist' 

没有用于更改工作目录的pathlib函数。您必须使用os.chdir()

os.getcwd()函数是获取当前工作目录字符串值的较旧方式。它在docs.python.org/3/library/os.html#os.getcwd中有文档说明。

访问主目录

所有用户在计算机上都有一个用于自己文件的文件夹;这个文件夹称为主目录主文件夹。您可以通过调用Path.home()来获取主文件夹的Path对象:

>>> from pathlib import Path
>>> Path.home()
WindowsPath('C:/Users/Al') 

主目录的位置取决于您的操作系统:

  • 在 Windows 上,主目录位于C:\Users

  • 在 macOS 上,主目录位于/Users

  • 在 Linux 上,主目录通常位于/home

您的脚本几乎肯定有权限读取和写入您主目录下的文件,因此这是一个放置您 Python 程序将与之交互的文件的理想位置。

指定绝对路径与相对路径

指定文件路径有两种方式:

  • 绝对路径,始终以根文件夹开始(Windows 上的C:*,macOS 和 Linux 上的/*)

  • 相对路径,相对于程序的当前工作目录

在 Windows 上,C:*是主硬盘的根目录。这种字母标记可以追溯到 20 世纪 60 年代,当时计算机有两个标记为A:*和B:*的软盘驱动器。在 Windows 上,USB 闪存驱动器和 DVD 驱动器被分配为D:*和更高的字母。使用这些驱动器之一作为根文件夹来访问该存储介质上的文件。

此外,还有点()和点点()文件夹。这些不是真正的文件夹,而是可以在文件路径中使用的特殊名称。文件夹名称中的一个点(点)是此文件夹的简称。两个点(点点)表示父文件夹

图 10-2 显示了某些示例文件夹和文件。当当前工作目录设置为C:\bacon时,其他文件夹和文件的相对路径设置如图所示。

一个显示嵌套文件夹和文件的图表,其中包含目录中每个级别的文件夹或文件的相对和绝对路径。

图 10-2:工作目录 C:\bacon 中文件夹和文件的相对路径描述

相对路径开头的.*是可选的。例如,.\spam.txtspam.txt*指向同一个文件。

创建新文件夹

您的程序可以使用os.makedirs()函数创建新的文件夹。在交互式 shell 中输入以下内容:

>>> import os
>>> os.makedirs('C:\\delicious\\walnut\\waffles') 

这将创建不仅包括C:\delicious文件夹,还包括C:\delicious中的walnut文件夹和C:\delicious\walnut中的waffles文件夹。也就是说,os.makedirs()将创建任何必要的中间文件夹,以确保完整路径存在。图 10-3 显示了这些文件夹的层次结构。

一个嵌套的目录结构。文件夹“C:\”包含文件夹“delicious”,它包含文件夹“walnut”,它包含文件夹“waffles”。

图 10-3:os.makedirs('C:\\delicious\\ walnut\\waffles')的结果

要从Path对象创建目录,请调用mkdir()方法。例如,以下代码将在我的计算机上的主文件夹下创建一个spam文件夹:

>>> from pathlib import Path
>>> Path(r'C:\Users\Al\spam').mkdir() 

注意,mkdir()一次只能创建一个目录,除非您传递parents=True,在这种情况下,它还会创建所有必要的父文件夹。

处理绝对路径和相对路径

Path对象上调用is_absolute()方法将返回True,如果它表示绝对路径,或者返回False,如果它表示相对路径。例如,在交互式 shell 中输入以下内容,使用您自己的文件和文件夹而不是这里列出的确切文件和文件夹:

>>> from pathlib import Path
>>> Path.cwd()
WindowsPath('C:/Users/Al/AppData/Local/Programs/Python/Python312')
>>> Path.cwd().is_absolute()
True
>>> Path('spam/bacon/eggs').is_absolute()
False 

要从相对路径获取绝对路径,您可以在相对Path对象前加上Path.cwd() /。毕竟,当我们说“相对路径”时,我们几乎总是指相对于当前工作目录的路径。absolute()方法也返回此Path对象。在交互式 shell 中输入以下内容:

>>> from pathlib import Path
>>> Path('my/relative/path')
WindowsPath('my/relative/path')
>>> Path.cwd() / Path('my/relative/path')
WindowsPath('C:/Users/Al/Desktop/my/relative/path')
>>> Path('my/relative/path').absolute()
WindowsPath('C:/Users/Al/Desktop/my/relative/path') 

如果您的相对路径相对于当前工作目录之外的另一个路径,请将Path.cwd()替换为那个其他路径。以下示例使用主目录而不是当前工作目录来获取绝对路径:

>>> from pathlib import Path
>>> Path('my/relative/path')
WindowsPath('my/relative/path')
>>> Path.home() / Path('my/relative/path')
WindowsPath('C:/Users/Al/my/relative/path') 

Path对象用于表示相对路径和绝对路径。唯一的区别是Path对象是否以根文件夹开头。

获取文件路径的部分

给定一个 Path 对象,您可以使用几个 Path 对象属性提取文件路径的不同部分作为字符串。这些对于根据现有内容构建新的文件路径非常有用。图 10-4 图解了这些属性。

Windows 和 macOS 上文件路径的图解,显示锚点、父文件夹、名称、驱动器、主体和后缀。

图 10-4:Windows(顶部)和 macOS/Linux(底部)文件路径的部分描述

文件路径的部分包括以下内容:

  • 锚点,即文件系统的根文件夹

  • 在 Windows 上,驱动器,通常表示物理硬盘驱动器或其他存储设备

  • 父文件夹,即包含文件的文件夹

  • 文件的 名称,由 主体(或 基本名称)和 后缀(或 扩展名)组成

注意,Windows Path 对象具有 drive 属性,但 macOS 和 Linux Path 对象没有。drive 属性不包括第一个反斜杠。

要从文件路径中提取每个属性,请在交互式外壳中输入以下内容:

>>> from pathlib import Path
>>> p = Path('C:/Users/Al/spam.txt')
>>> p.anchor
'C:\\'
>>> p.parent 
WindowsPath('C:/Users/Al')
>>> p.name
'spam.txt'
>>> p.stem
'spam'
>>> p.suffix
'.txt'
>>> p.drive
'C:' 

这些属性评估为简单的字符串值,除了 parent,它评估为另一个 Path 对象。如果您想通过其分隔符拆分路径,请访问 parts 属性以获取字符串值的元组:

>>> from pathlib import Path
>>> p = Path('C:/Users/Al/spam.txt')
>>> p.parts
('C:\\', 'Users', 'Al', 'spam.txt')
 >>> p.parts[3]
'spam.txt'
>>> p.parts[0:2]
('C:\\', 'Users') 

注意,尽管在 Path() 调用中使用的字符串包含正斜杠,但 parts 在 Windows 上使用锚点,该锚点具有适当的反斜杠:'C:\\'(或作为未转义反斜杠的原始字符串 r'C:\')。

parents 属性(与 parent 属性不同)评估为 Path 对象的祖先文件夹,具有整数索引:

>>> from pathlib import Path
>>> Path.cwd()
WindowsPath('C:/Users/Al/Desktop')
>>> Path.cwd().parents[0]
WindowsPath('C:/Users/Al')
>>> Path.cwd().parents[1]
WindowsPath('C:/Users')
>>> Path.cwd().parents[2]
WindowsPath('C:/') 

如果您继续跟踪父文件夹,最终会到达根文件夹。

查找文件大小和时间戳

一旦您有了处理文件路径的方法,您就可以开始收集有关特定文件和文件夹的信息。stat() 方法返回一个包含文件大小和时间戳信息的 stat_result 对象。

例如,在交互式外壳中输入以下内容,以了解 Windows 上的 calc.exe 程序文件:

>>> from pathlib import Path
>>> calc_file = Path('C:/Windows/System32/calc.exe')
>>> calc_file.stat()
os.stat_result(st_mode=33279, st_ino=562949956525418, st_dev=3739257218,
st_nlink=2, st_uid=0, st_gid=0, st_size=27648, st_atime=1678984560,
st_mtime=1575709787, st_ctime=1575709787)
>>> calc_file.stat().st_size
27648
>>> calc_file.stat().st_mtime
1712627129.0906117
>>> import time
>>> time.asctime(time.localtime(calc_file.stat().st_mtime))
'Mon Apr  8 20:45:29 2024' 

stat() 方法返回的 stat_result 对象的 st_size 属性是文件的字节数。您可以通过除以 10241024 ** 21024 ** 3 来分别获取 KB、MB 或 GB 的大小。

st_mtime 是“最后修改”时间戳,这对于确定 .docx Word 文件最后一次更改的时间非常有用。这个时间戳是 Unix 纪元时间,即自 1970 年 1 月 1 日以来的秒数。time 模块(在第十九章中解释)有将这个数字转换为可读形式的函数。

stat_result 对象具有几个有用的属性:

st_size 文件的大小(以字节为单位)。

st_mtime 文件最后更改的“最后修改”时间戳。

st_ctime “创建”时间戳。在 Windows 上,这标识文件创建的时间。在 macOS 和 Linux 上,这标识文件元数据(如名称)最后一次更改的时间。

st_atime 文件“最后访问”时间戳,即文件最后一次被读取的时间。

请记住,修改时间、创建时间和访问时间可以手动更改,并且不能保证准确。

使用 glob 模式查找文件

*?字符可以用于匹配所谓的glob 模式中的文件夹名和文件名。glob 模式类似于简化的正则表达式语言:*字符匹配任何文本,而?字符匹配恰好一个字符。例如,看看这些 glob 模式:

'*.txt'匹配所有以*.txt 结尾的文件。

'project?.txt'匹配'project1.txt''project2.txt''projectX.txt'

'*project?.*'匹配'catproject5.txt''secret_project7.docx'

'*'匹配所有文件名。

文件夹的Path对象有一个glob()方法,用于列出文件夹中匹配 glob 模式的任何内容。glob()方法返回一个生成器对象(其主题超出了本书的范围),你需要将其传递给list()以便在交互式 shell 中轻松查看:

>>> from pathlib import Path
>>> p = Path('C:/Users/Al/Desktop')
>>> p.glob('*')
<generator object Path.glob at 0x000002A6E389DED0>
>>> list(p.glob('*'))
[WindowsPath('C:/Users/Al/Desktop/1.png'), WindowsPath('C:/Users/Al/
Desktop/22-ap.pdf'), WindowsPath('C:/Users/Al/Desktop/cat.jpg'),
WindowsPath('C:/Users/Al/Desktop/zzz.txt')] 

你还可以在for循环中使用glob()返回的生成器对象:

>>> from pathlib import Path
>>> for name in Path('C:/Users/Al/Desktop').glob('*'):
>>>     print(name)
C:\Users\Al\Desktop\1.png
C:\Users\Al\Desktop\22-ap.pdf
C:\Users\Al\Desktop\cat.jpg
C:\Users\Al\Desktop\zzz.txt 

如果你想对一个文件夹中的每个文件执行操作,例如将其复制到备份文件夹或重命名,可以使用glob('*')方法调用获取这些文件和文件夹的Path对象列表。请注意,glob 模式也常用于命令行命令,如lsdir。第十二章将更详细地讨论命令行。

检查路径有效性

如果你向 Python 函数提供一个不存在的路径,许多函数会因错误而崩溃。幸运的是,Path对象有方法来检查给定路径是否存在以及它是否是文件或文件夹。假设一个变量p包含一个Path对象,你可以期待以下结果:

  • 调用p.exists()如果路径存在,则返回True,如果不存在,则返回False

  • 调用p.is_file()如果路径存在且是文件,则返回True,否则返回False

  • 调用p.is_dir()如果路径存在且是目录,则返回True,否则返回False

在我的计算机上,当我尝试在交互式 shell 中使用这些方法时,我得到了以下结果:

>>> from pathlib import Path
>>> win_dir = Path('C:/Windows')
>>> not_exists_dir = Path('C:/This/Folder/Does/Not/Exist')
>>> calc_file_path = Path('C:/Windows
/System32/calc.exe')
>>> win_dir.exists()
True
>>> win_dir.is_dir()
True
>>> not_exists_dir.exists()
False
>>> calc_file_path.is_file()
True
>>> calc_file_path.is_dir()
False 

你可以通过使用exists()方法来检查计算机上是否已连接 DVD 或 U 盘。例如,如果我想检查我的 Windows 计算机上名为*D:*的 U 盘,我可以使用以下方法:

>>> from pathlib import Path
>>> d_drive = Path('D:/')
>>> d_drive.exists()
False 

哎呀!看起来我忘记插入我的 U 盘了。

较旧的 os.path 模块可以通过 os.path.exists(path)os.path.isfile(path)os.path.isdir(path) 函数完成相同的任务,这些函数的行为就像它们的 Path 函数对应物一样。从 Python 3.6 开始,这些函数可以接受 Path 对象以及文件路径的字符串。

文件读写过程

一旦你熟悉了文件夹和相对路径的工作方式,你将能够指定读取和写入文件的地点。下一几节中介绍的功能适用于纯文本文件。纯文本文件 只包含基本的文本字符,不包含字体、大小或颜色信息。具有 .txt 扩展名的文本文件或具有 .py 扩展名的 Python 脚本文件是纯文本文件的例子。你可以使用 Windows 记事本或 macOS TextEdit 应用程序打开这些文件,然后你的程序可以轻松读取它们的内容,并将它们作为普通的字符串值处理。

二进制文件 是所有其他类型的文件,例如文档处理文件、PDF 文件、图像、电子表格和可执行程序。如果你在记事本或 TextEdit 中打开一个二进制文件,它看起来就像混乱的胡言乱语,就像图 10-5 所示的那样。

包含无法理解的字符和源代码的记事本窗口

图 10-5:在记事本中打开的 Windows calc.exe 程序

由于我们必须以自己的方式处理每种类型的二进制文件,因此本书不会讨论直接读取和写入原始二进制文件。幸运的是,许多模块使处理二进制文件变得更加容易,你将在本章后面探索其中之一,即 shelve 模块。

pathlib 模块的 read_text() 方法返回文本文件的全部内容作为一个字符串。它的 write_text() 方法使用传递给它的字符串创建一个新的文本文件(或覆盖现有的一个)。在交互式外壳中输入以下内容:

>>> from pathlib import Path
>>> p = Path('spam.txt')
>>> p.write_text('Hello, world!')
13
>>> p.read_text()
'Hello, world!' 

这些方法调用会创建一个名为 spam.txt 的文件,其内容为 'Hello, world!'write_text() 返回的 13 表示有 13 个字符被写入文件。(你通常可以忽略这个返回值。)read_text() 调用读取并返回新文件的全部内容作为一个字符串:'Hello, world!'

请记住,这些 Path 对象方法只允许与文件进行基本交互。将文件写入的更常见方式是使用 open() 函数和文件对象。在 Python 中读取或写入文件有三个步骤:

1.  调用 open() 函数以返回一个 File 对象。

2.  在 File 对象上调用 read()write() 方法。

3.  通过在 File 对象上调用 close() 方法来关闭文件。

我们将在接下来的几节中介绍这些步骤。

注意,当你开始处理文件时,你可能发现能够快速查看它们的扩展名(如 .txt、.pdf、.jpg 等)很有帮助。Windows 和 macOS 默认可能会隐藏文件扩展名,将 spam.txt 显示为简单的 spam。要显示扩展名,请打开文件资源管理器(在 Windows 上)或 Finder(在 macOS 上)的设置,并查找一个类似于“显示所有文件名扩展名”或“隐藏已知文件类型的扩展名”的复选框。(此设置的精确位置和措辞取决于你的操作系统版本。)

打开文件

要使用 open() 函数打开文件,传递一个表示你想要打开的文件的字符串路径。这可以是绝对路径或相对路径。open() 函数返回一个 File 对象。

通过创建一个名为 hello.txt 的文本文件来尝试此操作,使用记事本或 TextEdit。将 Hello, world! 作为此文本文件的内容,并将其保存在你的用户主目录中。然后,在交互式 shell 中输入以下内容:

>>> from pathlib import Path
>>> hello_file = open(Path.home() / 'hello.txt', encoding='UTF-8') 

open() 函数将以“读取纯文本”模式打开文件,或简称为 读取模式。当文件以读取模式打开时,Python 允许你读取文件的数据,但不能以任何方式写入或修改它。读取模式是你在 Python 中打开文件的默认模式。但如果你不想依赖 Python 的默认设置,可以通过将字符串值 'r' 作为 open() 函数的第二个参数显式指定模式。例如,open('/Users/Al/hello.txt', 'r')open('/Users/Al/hello.txt') 做的是同样的事情。

encoding 参数指定在将文件中的字节转换为 Python 文本字符串时使用哪种编码。正确的编码几乎总是 'utf-8',这也是 macOS 和 Linux 上使用的默认编码。然而,Windows 使用 'cp1252' 作为其默认编码(也称为 扩展 ASCII)。这可能导致在 Windows 上尝试读取具有非英语字符的某些 UTF-8 编码文本文件时出现问题,因此,在以纯文本读取、写入或追加模式打开文件时,将 encoding='utf-8' 传递给 open() 函数调用是一个好习惯。二进制读取、写入和追加模式不使用 encoding 参数,因此在这些情况下可以省略它。

open() 函数的调用返回一个 File 对象。File 对象代表你电脑上的文件;它只是 Python 中另一种类型的值,就像你已熟悉的列表和字典一样。在上一个例子中,你将 File 对象存储在变量 hello_file 中。现在,无论何时你想从文件中读取或写入,你都可以通过在 hello_file 中的 File 对象上调用方法来实现。

读取文件内容

现在你已经有一个 File 对象,你可以开始从中读取。如果你想将整个文件内容作为字符串值读取,请使用 File 对象的 read() 方法。让我们继续使用你存储在 hello_file 中的 hello.txt File 对象。在交互式 shell 中输入以下内容:

>>> hello_content = hello_file.read()
>>> hello_content
'Hello, world!' 

你可以将文件的内容视为单个大字符串值;read() 方法只是返回存储在文件中的字符串。

或者,你可以使用 readlines() 方法从文件中获取字符串值的 列表,每行文本一个。例如,在 hello.txt 所在的目录中创建一个名为 sonnet29.txt 的文件,并在其中放置以下文本:

When, in disgrace with fortune and men's eyes,
I all alone beweep my outcast state,
And trouble deaf heaven with my bootless cries,
And look upon myself and curse my fate, 

确保使用换行符分隔这四行。然后,在交互式外壳中输入以下内容:

>>> sonnet_file = open(Path.home() / 'sonnet29.txt', encoding='UTF-8')
>>> sonnet_file.readlines()
["When, in disgrace with fortune and men's eyes,\n", 'I all alone beweep
my outcast state,\n', 'And trouble deaf heaven with my bootless cries,\n',
'And look upon myself and curse my fate,'] 

注意,除了文件的最后一行外,每个字符串值都以换行符 \n 结尾。字符串列表通常比单个大字符串值更容易处理。

写入文件

Python 允许你将内容写入文件,就像 print() 函数将字符串写入屏幕一样。但是,你不能在以读取模式打开的文件中写入。相反,你需要以“写入纯文本”模式或“追加纯文本”模式打开它,简称为 写入模式追加模式

写入模式将覆盖现有文件,这类似于用新值覆盖变量的值。将 'w' 作为 open() 的第二个参数传递以以写入模式打开文件。另一方面,追加模式将文本追加到现有文件的末尾。你可以将此模式视为将值追加到变量的列表中而不是完全覆盖变量。将 'a' 作为 open() 的第二个参数传递以以追加模式打开文件。

如果传递给 open() 的文件名不存在,则写入模式和追加模式都会创建一个新的空白文件。在读取或写入文件后,在再次打开文件之前调用 close() 方法。

让我们将这些概念结合起来。在交互式外壳中输入以下内容:

>>> bacon_file = open('bacon.txt', 'w', encoding='UTF-8') 
>>> bacon_file.write('Hello, world!\n')
14
>>> bacon_file.close()
>>> bacon_file = open('bacon.txt', 'a', encoding='UTF-8') 
>>> bacon_file.write('Bacon is not a vegetable.')
25
>>> bacon_file.close()
>>> bacon_file = open('bacon.txt', encoding='UTF-8')
>>> content = bacon_file.read()
>>> bacon_file.close()
>>> print(content)
Hello, world!
Bacon is not a vegetable. 

首先,我们以写入模式打开 bacon.txt。由于尚不存在 bacon.txt 文件,Python 将创建一个。在打开的文件上调用 write() 并将字符串参数 'Hello, world!\n' 传递给 write(),将字符串写入文件并返回写入的字符数,包括换行符。然后,我们关闭文件。

要将文本添加到文件的现有内容中而不是替换我们刚刚写入的字符串,我们以追加模式打开文件。我们将 'Bacon is not a vegetable.' 写入文件并关闭它。最后,为了将文件内容打印到屏幕上,我们以默认的读取模式打开文件,调用 read(),将结果 File 对象存储在 content 中,关闭文件,并打印 content

注意,write() 方法不会像 print() 函数那样自动在字符串末尾添加换行符。你必须自己添加这个字符。

你也可以将 Path 对象传递给 open() 函数,而不是作为字符串的文件名。

使用 with 语句

你程序中调用 open() 的每个文件都需要调用 close(),但你可能忘记包含 close() 函数,或者你的程序可能在某些情况下跳过了 close() 调用。

Python 的 with 语句使得自动关闭文件变得更加容易。with 语句创建了一个名为 上下文管理器 的东西,Python 使用它来管理资源。这些资源,如文件、网络连接或内存段,在分配和随后释放资源以供其他程序使用的过程中,通常会有设置和拆除步骤。(然而,大多数情况下,你将遇到用于打开文件的 with 语句。)

with 语句添加了一块代码,它首先分配资源,然后在程序执行离开该块时释放资源,这可能是由于 return 语句、未处理的异常被抛出或其他原因。

下面是写入和读取文件内容的典型代码:

file_obj = open('data.txt', 'w', encoding='utf-8')
file_obj.write('Hello, world!')
file_obj.close()
file_obj = open('data.txt', encoding='utf-8')
content = file_obj.read()
file_obj.close() 

这里是使用 with 语句的等效代码:

with open('data.txt', 'w', encoding='UTF-8') as file_obj:
    file_obj.write('Hello, world!')
with open('data.txt', encoding='UTF-8') as file_obj:
    content = file_obj.read() 

with 语句的示例中,请注意,根本没有任何 close() 调用,因为 with 语句在程序执行离开块时自动调用它。with 语句知道这样做是因为它从 open() 函数获得的上下文管理器。创建自己的上下文管理器超出了本书的范围,但你可以在在线文档中了解它们,网址为 docs.python.org/3/reference/datamodel.html#context-managers 或者在 Julien Danjou 的书籍 Serious Python(No Starch Press,2018 年)中了解。

打开文件

要使用 open() 函数打开文件,传递一个表示你想要打开的文件的字符串路径。这可以是绝对路径或相对路径。open() 函数返回一个 File 对象。

通过使用记事本或 TextEdit 创建一个名为 hello.txt 的文本文件来尝试这个方法。将 Hello, world! 作为此文本文件的内容,并将其保存在你的用户主目录中。然后,在交互式外壳中输入以下内容:

>>> from pathlib import Path
>>> hello_file = open(Path.home() / 'hello.txt', encoding='UTF-8') 

open() 函数将以“读取纯文本”模式打开文件,或简称为 读取模式。当文件以读取模式打开时,Python 允许你读取文件数据,但不能以任何方式写入或修改它。读取模式是 Python 中打开文件的默认模式。但如果你不想依赖 Python 的默认设置,你可以通过将字符串值 'r' 作为 open() 的第二个参数显式指定模式。例如,open('/Users/Al/hello.txt', 'r')open('/Users/Al/hello.txt') 做的是同样的事情。

encoding 参数指定在将文件中的字节转换为 Python 文本字符串时使用哪种编码。正确的编码几乎总是 'utf-8',这也是 macOS 和 Linux 上使用的默认编码。然而,Windows 使用 'cp1252' 作为其默认编码(也称为 扩展 ASCII)。这可能会在尝试在 Windows 上读取某些 UTF-8 编码的包含非英语字符的文本文件时引起问题,因此,在以纯文本读取、写入或追加模式打开文件时传递 encoding='utf-8' 是一个好习惯。二进制读取、写入和追加模式不使用 encoding 参数,因此在这些情况下可以省略。

open() 函数的调用返回一个 File 对象。File 对象代表计算机上的文件;它只是 Python 中的一种值类型,类似于你已经熟悉的列表和字典。在之前的例子中,你将 File 对象存储在变量 hello_file 中。现在,无论何时你想从文件中读取或写入,你都可以通过在 hello_file 中的 File 对象上调用方法来实现。

读取文件内容

现在你有了 File 对象,你可以开始从中读取。如果你想将整个文件内容作为字符串值读取,请使用 File 对象的 read() 方法。让我们继续使用你存储在 hello_file 中的 hello.txt File 对象。在交互式 shell 中输入以下内容:

>>> hello_content = hello_file.read()
>>> hello_content
'Hello, world!' 

你可以将文件的内容想象成一个单独的大字符串值;read() 方法只是返回存储在文件中的字符串。

或者,你可以使用 readlines() 方法从文件中获取一个字符串值的列表,每个文本行对应一个。例如,在 hello.txt 所在的目录中创建一个名为 sonnet29.txt 的文件,并在其中放置以下文本:

When, in disgrace with fortune and men's eyes,
I all alone beweep my outcast state,
And trouble deaf heaven with my bootless cries,
And look upon myself and curse my fate, 

确保用换行符分隔这四行。然后,在交互式 shell 中输入以下内容:

>>> sonnet_file = open(Path.home() / 'sonnet29.txt', encoding='UTF-8')
>>> sonnet_file.readlines()
["When, in disgrace with fortune and men's eyes,\n", 'I all alone beweep
my outcast state,\n', 'And trouble deaf heaven with my bootless cries,\n',
'And look upon myself and curse my fate,'] 

注意,除了文件的最后一条记录外,每个字符串值都以换行符 \n 结尾。通常,与单个大字符串值相比,字符串列表更容易处理。

写入文件

Python 允许你将内容写入文件,就像 print() 函数将字符串写入屏幕一样。但是,你不能在以读取模式打开的文件中写入。相反,你需要以“写入纯文本”模式或“追加纯文本”模式(简称 写入模式追加模式)打开它。

写入模式将覆盖现有文件,这类似于用新值覆盖变量的值。将 'w' 作为 open() 的第二个参数传递以在写入模式下打开文件。另一方面,追加模式将文本追加到现有文件的末尾。你可以将此模式视为将值追加到变量的列表中,而不是完全覆盖变量。将 'a' 作为 open() 的第二个参数传递以在追加模式下打开文件。

如果传递给 open() 的文件名不存在,则写入模式和追加模式都会创建一个新的空文件。在读取或写入文件后,在再次打开文件之前调用 close() 方法。

让我们将这些概念结合起来。在交互式外壳中输入以下内容:

>>> bacon_file = open('bacon.txt', 'w', encoding='UTF-8') 
>>> bacon_file.write('Hello, world!\n')
14
>>> bacon_file.close()
>>> bacon_file = open('bacon.txt', 'a', encoding='UTF-8') 
>>> bacon_file.write('Bacon is not a vegetable.')
25
>>> bacon_file.close()
>>> bacon_file = open('bacon.txt', encoding='UTF-8')
>>> content = bacon_file.read()
>>> bacon_file.close()
>>> print(content)
Hello, world!
Bacon is not a vegetable. 

首先,我们以写入模式打开 bacon.txt。由于尚不存在 bacon.txt 文件,Python 将创建一个。在打开的文件上调用 write() 并将字符串参数 'Hello, world!\n' 传递给 write() 将字符串写入文件,并返回写入的字符数,包括换行符。然后,我们关闭文件。

要向现有文件内容中添加文本而不是替换我们刚刚写入的字符串,我们以追加模式打开文件。我们将 'Bacon is not a vegetable.' 写入文件并关闭它。最后,为了将文件内容打印到屏幕上,我们以默认的读取模式打开文件,调用 read(),将结果 File 对象存储在 content 中,关闭文件,并打印 content

注意,write() 方法不会像 print() 函数那样自动在字符串末尾添加换行符。你必须自己添加这个字符。

你也可以将Path对象传递给open()函数,而不是作为字符串的文件名。

使用 with 语句

你程序中调用 open() 的每个文件都需要调用 close(),但你可能忘记包含 close() 函数,或者你的程序可能在某些情况下跳过 close() 调用。

Python 的 with 语句使得自动关闭文件变得更加容易。with 语句创建了一个名为 上下文管理器 的东西,Python 使用它来管理资源。这些资源,如文件、网络连接或内存段,在设置和拆除步骤中通常会分配资源并在之后释放,以便其他程序可以使用它。(然而,大多数情况下,你将遇到用于打开文件的 with 语句。)

with 语句添加了一块代码块,该代码块首先分配资源,然后在程序执行离开该代码块时释放资源,这可能是由于return语句、未处理的异常被抛出或其他原因。

这里是写入和读取文件内容的典型代码:

file_obj = open('data.txt', 'w', encoding='utf-8')
file_obj.write('Hello, world!')
file_obj.close()
file_obj = open('data.txt', encoding='utf-8')
content = file_obj.read()
file_obj.close() 

这里是使用 with 语句的等效代码:

with open('data.txt', 'w', encoding='UTF-8') as file_obj:
    file_obj.write('Hello, world!')
with open('data.txt', encoding='UTF-8') as file_obj:
    content = file_obj.read() 

with语句的示例中,注意没有任何地方调用close(),因为with语句在程序执行离开代码块时会自动调用它。with语句知道这样做是因为它从open()函数获得的上下文管理器。创建自己的上下文管理器超出了本书的范围,但你可以在docs.python.org/3/reference/datamodel.html#context-managers或 Julien Danjou 的书籍Serious Python(No Starch Press,2018 年)中了解它们。

使用shelve模块保存变量

你可以使用shelve模块将变量保存到二进制 shelf 文件中。这样,当程序下次运行时,它可以恢复这些数据到变量中。你可以使用这种技术为你的程序添加保存和打开功能;例如,如果你运行了一个程序并输入了一些配置设置,你可以将这些设置保存到 shelf 文件中,然后在程序下次运行时加载这些设置。

为了练习使用shelve,在交互式 shell 中输入以下内容:

>>> import shelve
>>> shelf_file = shelve.open('mydata')

>>> shelf_file['cats'] = ['Zophie', 'Pooka', 'Simon']
>>> shelf_file.close() 

使用shelve模块读取和写入数据时,首先需要导入shelve。接下来,调用shelve.open()并传入一个文件名,然后将返回的 shelf 值存储在一个变量中。你可以像操作字典一样修改 shelf 值。完成操作后,对 shelf 值调用close()方法。在这里,我们的 shelf 值存储在shelf_file中。我们创建一个列表cats,并将shelf_file['cats'] = ['Zophie', 'Pooka', 'Simon']写入shelf_file,将列表作为与键'cats'关联的值存储(就像在字典中一样)。然后,我们对shelf_file调用close()

在 Windows 上运行前面的代码后,你应该在当前工作目录中看到三个新文件:mydata.bakmydata.datmydata.dir。在 macOS 上,你应该只看到一个mydata.db文件,而在 Linux 上只有一个mydata文件。这些二进制文件包含你存储在 shelf 中的数据。这些二进制文件的格式并不重要;你只需要知道shelve模块做什么,而不是它是如何做的。该模块让你从担心如何将程序数据存储到文件中解脱出来。

你的程序可以使用shelve模块稍后重新打开并检索这些 shelf 文件中的数据。shelf 值打开后不需要以读或写模式打开;一旦打开,它们允许读写。在交互式 shell 中输入以下内容:

>>> shelf_file = shelve.open('mydata')
>>> type(shelf_file) 
<class 'shelve.DbfilenameShelf'>
>>> shelf_file['cats']
['Zophie', 'Pooka', 'Simon']
>>> shelf_file.close() 

在这里,我们打开 shelf 文件以检查它们是否正确存储了数据。输入shelf_file['cats']返回我们之前创建的相同列表。现在我们知道文件正确存储了列表,我们调用close()

就像字典一样,shelf 值有keys()values()方法,可以返回类似列表的键和值。因为这些返回值不是真正的列表,你应该将它们传递给list()函数以获取列表形式。在交互式外壳中输入以下内容:

>>> shelf_file = shelve.open('mydata')
>>> list(shelf_file.keys())
['cats']
>>> list(shelf_file.values())
[['Zophie', 'Pooka', 'Simon']]
>>> shelf_file.close() 

纯文本对于创建你将在文本编辑器(如记事本或 TextEdit)中读取的文件很有用,但如果你想从 Python 程序中保存数据,请使用shelve模块。

项目 4:生成随机测验文件

假设你是一名地理老师,班上有 35 名学生,你想对美国州首府进行一次即兴测验。唉,你的班上有些学生不太可靠,你无法信任学生不会作弊。你希望随机排列问题的顺序,使每个测验都是独一无二的,这样任何人都不可能从别人那里抄袭答案。当然,手动做这件事会是一个漫长而无聊的过程。幸运的是,你懂一些 Python。

程序执行如下:

  • 创建 35 个不同的测验

  • 为每个测验创建 50 个多项选择题,顺序随机

  • 为每个问题提供正确答案和三个随机错误的答案,顺序随机

  • 将测验写入 35 个文本文件。

  • 将答案键写入 35 个文本文件。

这意味着代码需要执行以下操作:

  • 在字典中存储州及其首府。

  • 对测验和答案键文本文件调用open()write()close()

  • 使用random.shuffle()随机排列问题和多项选择题的顺序。

让我们开始吧。

第 1 步:将测验数据存储在字典中

第一步是创建一个脚本来创建框架,并填充你的测验数据。创建一个名为randomQuizGenerator.py的文件,并使其看起来像以下内容:

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

import random # ❶

# The quiz data. Keys are states and values are their capitals.
capitals = {'Alabama': 'Montgomery', 'Alaska': 'Juneau', 'Arizona': # ❷
'Phoenix', 'Arkansas': 'Little Rock', 'California': 'Sacramento', 'Colorado':
'Denver', 'Connecticut': 'Hartford', 'Delaware': 'Dover', 'Florida':
'Tallahassee', 'Georgia': 'Atlanta', 'Hawaii': 'Honolulu', 'Idaho': 'Boise',
'Illinois': 'Springfield', 'Indiana': 'Indianapolis', 'Iowa': 'Des Moines',
'Kansas': 'Topeka', 'Kentucky': 'Frankfort', 'Louisiana': 'Baton Rouge',
'Maine': 'Augusta', 'Maryland': 'Annapolis', 'Massachusetts': 'Boston',
'Michigan': 'Lansing', 'Minnesota': 'Saint Paul', 'Mississippi': 'Jackson',
'Missouri': 'Jefferson City', 'Montana': 'Helena', 'Nebraska': 'Lincoln',
'Nevada': 'Carson City', 'New Hampshire': 'Concord', 'New Jersey': 'Trenton',
'New Mexico': 'Santa Fe', 'New York': 'Albany', 'North Carolina': 'Raleigh',
'North Dakota': 'Bismarck', 'Ohio': 'Columbus', 'Oklahoma': 'Oklahoma City',
'Oregon': 'Salem', 'Pennsylvania': 'Harrisburg', 'Rhode Island': 'Providence',
'South Carolina': 'Columbia', 'South Dakota': 'Pierre', 'Tennessee':
'Nashville', 'Texas': 'Austin', 'Utah': 'Salt Lake City', 'Vermont':
'Montpelier', 'Virginia': 'Richmond', 'Washington': 'Olympia', 'West
Virginia':'Charleston', 'Wisconsin': 'Madison', 'Wyoming': 'Cheyenne'}

# Generate 35 quiz files.
for quiz_num in range(35): # ❸
    # TODO: Create the quiz and answer key files.

    # TODO: Write out the header for the quiz.

    # TODO: Shuffle the order of the states.

    # TODO: Loop through all 50 states, making a question for each. 

因为这个程序将随机排列问题和答案,你需要导入random模块❶以使用其功能。capitals变量❷包含一个字典,以美国州为键,首府为值。而且因为你想要创建 35 个测验,实际生成测验和答案键文件的代码(目前用TODO注释标记)将放在一个循环中,该循环循环 35 次❸。(你可以更改这个数字以生成任何数量的测验文件。)

第 2 步:创建测验文件

现在是时候开始填写那些TODO了。

循环中的代码将重复 35 次,每次对应一个测验,所以你只需要在循环中一次只担心一个测验。首先,你需要创建实际的测验文件。它需要一个唯一的文件名和一些标准标题,包括学生填写姓名、日期和课程时段的地方。然后,你需要获取一个随机顺序的州列表,你可以稍后使用它来创建测验的问题和答案。

将以下代码行添加到randomQuizGenerator.py

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

# --snip--

# Generate 35 quiz files.
for quiz_num in range(35):
    # Create the quiz and answer key files.
    quiz_file = open(f'capitalsquiz{quiz_num + 1}.txt', 'w', encoding='UTF-8') ❶
    answer_file = open(f'capitalsquiz_answers{quiz_num + 1}.txt', 'w', encoding='UTF-8') ❷

    # Write out the header for the quiz.
    quiz_file.write('Name:\n\nDate:\n\nPeriod:\n\n') ❸
    quiz_file.write((' ' * 20) + f'State Capitals Quiz (Form{quiz_num + 1})')
    quiz_file.write('\n\n')

    # Shuffle the order of the states.
 states = list(capitals.keys())
    random.shuffle(states) ❹

    # TODO: Loop through all 50 states, making a question for each. 

测验将使用 capitalsquiz.txt 文件名,其中 是来自 quiz_num 的唯一数字,它是 for 循环的计数器。我们将 capitalsquiz.txt 的答案键存储在名为 capitalsquiz_answers.txt 的文本文件中。在循环的每次迭代中,代码将用唯一数字替换这些文件名中的 {quiz _num + 1} 占位符。例如,它将第一个测验和答案键命名为 capitalsquiz1.txtcapitalsquiz _answers1.txt。我们通过调用 open() 函数创建这些文件,并将 'w' 作为第二个参数传递以写入模式打开它们。

在 ❸ 处的 write() 语句创建一个供学生填写的测验标题。最后,我们使用 random.shuffle() 函数 ❹ 帮助生成一个随机化的美国州列表,该函数随机重新排列传递给它的任何列表中的值。

第 3 步:创建答案选项

现在您需要使用另一个 for 循环为每个问题生成选项 A 到 D。稍后,第三个嵌套的 for 循环将把这些多项选择题选项写入文件。使您的代码看起来如下所示:

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

# --snip--

 # Loop through all 50 states, making a question for each.
 for num in range(50):

        # Get right and wrong answers.
 correct_answer = capitals[states[num]]
 wrong_answers = list(capitals.values())
 del wrong_answers[wrong_answers.index(correct_answer)]
 wrong_answers = random.sample(wrong_answers, 3)
 answer_options = wrong_answers + [correct_answer]
 random.shuffle(answer_options)

        # TODO: Write the question and answer options to the quiz file.

        # TODO: Write the answer key to a file. 

创建正确答案很简单;它已经作为 capitals 字典中的值存储。这个循环将遍历打乱顺序的 states 列表中的州,在 capitals 中找到每个州,并将该州对应的首都存储在 correct_answer 中。

创建可能的错误答案列表比较复杂。您可以通过复制 capitals 字典中的值,删除正确答案,并从该列表中随机选择三个值来获得它。random.sample() 函数使得执行此选择变得容易。它的第一个参数是要从中选择的列表,第二个参数是要选择的值的数量。完整的答案选项列表将这三个错误答案与正确答案结合起来。最后,我们随机化答案,以确保正确答案不总是选择 D。

第 4 步:将内容写入文件

剩下的就是将问题写入测验文件,将答案写入答案键文件。使您的代码看起来如下所示:

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

# --snip--

    # Loop through all 50 states, making a question for each.
    for num in range(50):
        # --snip--

        # Write the question and the answer options to the quiz file.
        quiz_file.write(f'{num + 1}. Capital of {states[num]}:\n')
          for i in range(4): # ❶
              for i in range(4): # ❶
") 
        quiz_file.write('\n')

        # Write the answer key to a file.
          for i in range(4): # ❶
    quiz_file.close()
    answer_file.close() 

一个 for 循环遍历整数 03,将 answer_options 列表中的答案选项写入文件 ❶。在 ❷ 处的 'ABCD'[i] 表达式将字符串 'ABCD' 作为数组处理,并在循环的每次迭代中评估为 'A''B''C''D'

在循环的最后一行,表达式 answer_options.index(correct_answer) ❸ 将找到正确答案在随机排序的答案选项中的整数索引,导致正确答案的字母被写入答案键文件。

运行程序后,您的 capitalsquiz1.txt 文件应该看起来像这样。当然,您的问题和答案选项将取决于您的 random.shuffle() 调用的结果:

Name:

Date:

Period:

                    State Capitals Quiz (Form 1)

1\. What is the capital of West Virginia?
    A. Hartford
    B. Santa Fe
    C. Harrisburg
    D. Charleston

2\. What is the capital of Colorado?
    A. Raleigh
    B. Harrisburg
    C. Denver
    D. Lincoln

# --snip-- 

对应的 capitalsquiz_answers1.txt 文本文件将看起来像这样:

1\. D
2\. C

# --snip-- 

手动随机排序问题集和相应的答案键将花费数小时,但只要有一点编程知识,你就可以自动化这项无聊的任务,不仅限于州首府测验,还可以用于任何多项选择题。

第 1 步:将测验数据存储在字典中

第一步是创建一个脚本来构建框架,并用你的测验数据填充它。创建一个名为 randomQuizGenerator.py 的文件,并使其看起来如下所示:

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

import random # ❶

# The quiz data. Keys are states and values are their capitals.
capitals = {'Alabama': 'Montgomery', 'Alaska': 'Juneau', 'Arizona': # ❷
'Phoenix', 'Arkansas': 'Little Rock', 'California': 'Sacramento', 'Colorado':
'Denver', 'Connecticut': 'Hartford', 'Delaware': 'Dover', 'Florida':
'Tallahassee', 'Georgia': 'Atlanta', 'Hawaii': 'Honolulu', 'Idaho': 'Boise',
'Illinois': 'Springfield', 'Indiana': 'Indianapolis', 'Iowa': 'Des Moines',
'Kansas': 'Topeka', 'Kentucky': 'Frankfort', 'Louisiana': 'Baton Rouge',
'Maine': 'Augusta', 'Maryland': 'Annapolis', 'Massachusetts': 'Boston',
'Michigan': 'Lansing', 'Minnesota': 'Saint Paul', 'Mississippi': 'Jackson',
'Missouri': 'Jefferson City', 'Montana': 'Helena', 'Nebraska': 'Lincoln',
'Nevada': 'Carson City', 'New Hampshire': 'Concord', 'New Jersey': 'Trenton',
'New Mexico': 'Santa Fe', 'New York': 'Albany', 'North Carolina': 'Raleigh',
'North Dakota': 'Bismarck', 'Ohio': 'Columbus', 'Oklahoma': 'Oklahoma City',
'Oregon': 'Salem', 'Pennsylvania': 'Harrisburg', 'Rhode Island': 'Providence',
'South Carolina': 'Columbia', 'South Dakota': 'Pierre', 'Tennessee':
'Nashville', 'Texas': 'Austin', 'Utah': 'Salt Lake City', 'Vermont':
'Montpelier', 'Virginia': 'Richmond', 'Washington': 'Olympia', 'West
Virginia':'Charleston', 'Wisconsin': 'Madison', 'Wyoming': 'Cheyenne'}

# Generate 35 quiz files.
for quiz_num in range(35): # ❸
    # TODO: Create the quiz and answer key files.

    # TODO: Write out the header for the quiz.

    # TODO: Shuffle the order of the states.

    # TODO: Loop through all 50 states, making a question for each. 

因为这个程序将随机排序问题和答案,你需要导入 random 模块 ❶ 来使用它的函数。capitals 变量 ❷ 包含一个字典,以美国州为键,以首都为值。而且因为你想要创建 35 个测验,实际生成测验和答案键文件的代码(目前用 TODO 注释标记)将放在一个循环中,该循环循环 35 次 ❸。(你可以更改这个数字以生成任何数量的测验文件。)

第 2 步:创建测验文件

现在是时候开始填写那些 TODOs 了。

循环中的代码将重复 35 次,每次对应一个测验,所以你只需要在循环中一次处理一个测验。首先,你将创建实际的测验文件。它需要一个唯一的文件名和一些标准标题,包括学生填写姓名、日期和课程时段的地方。然后,你需要获取一个随机顺序的州列表,你可以稍后使用它来创建测验的问题和答案。

将以下代码行添加到 randomQuizGenerator.py:

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

# --snip--

# Generate 35 quiz files.
for quiz_num in range(35):
    # Create the quiz and answer key files.
    quiz_file = open(f'capitalsquiz{quiz_num + 1}.txt', 'w', encoding='UTF-8') ❶
    answer_file = open(f'capitalsquiz_answers{quiz_num + 1}.txt', 'w', encoding='UTF-8') ❷

    # Write out the header for the quiz.
    quiz_file.write('Name:\n\nDate:\n\nPeriod:\n\n') ❸
    quiz_file.write((' ' * 20) + f'State Capitals Quiz (Form{quiz_num + 1})')
    quiz_file.write('\n\n')

    # Shuffle the order of the states.
 states = list(capitals.keys())
    random.shuffle(states) ❹

    # TODO: Loop through all 50 states, making a question for each. 

测验将使用文件名 capitalsquiz.txt,其中 是来自 quiz_num 的一个唯一数字,它是 for 循环的计数器。我们将 capitalsquiz.txt 的答案键存储在名为 capitalsquiz_answers.txt 的文本文件中。在循环的每次迭代中,代码将用唯一的数字替换这些文件名中的 {quiz _num + 1} 占位符。例如,它将第一个测验和答案键命名为 capitalsquiz1.txtcapitalsquiz _answers1.txt。我们通过调用 open() 函数 ❶ 和 ❷ 创建这些文件,将 'w' 作为第二个参数传递以写入模式打开。

位置的 write() 语句创建了一个学生需要填写的学生测验标题。最后,我们使用 random.shuffle() 函数 ❹ 生成一个随机的美国州列表,该函数随机重新排列传递给它的任何列表中的值。 # ❸

第 3 步:创建答案选项

现在您需要使用另一个 for 循环为每个问题生成答案选项 A 到 D。稍后,第三个嵌套的 for 循环将把这些多项选择题写入文件。让你的代码看起来如下所示:

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

# --snip--

 # Loop through all 50 states, making a question for each.
 for num in range(50):

        # Get right and wrong answers.
 correct_answer = capitals[states[num]]
 wrong_answers = list(capitals.values())
 del wrong_answers[wrong_answers.index(correct_answer)]
 wrong_answers = random.sample(wrong_answers, 3)
 answer_options = wrong_answers + [correct_answer]
 random.shuffle(answer_options)

        # TODO: Write the question and answer options to the quiz file.

        # TODO: Write the answer key to a file. 

正确答案很容易创建;它已经作为 capitals 字典中的值存储。这个循环将遍历打乱顺序的 states 列表中的各个州,在 capitals 中找到每个州,并将该州的对应首都存储在 correct_answer 中。

创建可能的错误答案列表比较复杂。你可以通过复制capitals字典中的值,删除正确答案,并从该列表中选择三个随机值来获取它。random.sample()函数使得执行这个选择变得简单。它的第一个参数是你想要从中选择的列表,第二个参数是你想要选择的值的数量。完整的答案选项列表将这三个错误答案与正确答案结合起来。最后,我们随机化答案,以确保正确答案不总是选择 D。

第 4 步:将内容写入文件

剩下的就是将问题写入测验文件,答案写入答案键文件。让你的代码看起来像下面这样:

# randomQuizGenerator.py - Creates quizzes with questions and answers in
# random order, along with the answer key

# --snip--

    # Loop through all 50 states, making a question for each.
    for num in range(50):
        # --snip--

        # Write the question and the answer options to the quiz file.
        quiz_file.write(f'{num + 1}. Capital of {states[num]}:\n')
          for i in range(4): # ❶
              for i in range(4): # ❶
") 
        quiz_file.write('\n')

        # Write the answer key to a file.
          for i in range(4): # ❶
    quiz_file.close()
    answer_file.close() 

for循环遍历整数03,将answer_options列表中的答案选项写入文件❶。在❷处的表达式'ABCD'[i]将字符串'ABCD'当作数组处理,并在循环的每次迭代中分别评估为'A''B''C''D'

在循环的最后一行,表达式answer_options.index(correct_answer)❸将找到随机排序的答案选项中正确答案的整数索引,导致正确答案的字母被写入答案键文件。

运行程序后,你的capitalsquiz1.txt文件应该看起来像这样。当然,你的问题和答案选项将取决于你的random.shuffle()调用结果:

Name:

Date:

Period:

                    State Capitals Quiz (Form 1)

1\. What is the capital of West Virginia?
    A. Hartford
    B. Santa Fe
    C. Harrisburg
    D. Charleston

2\. What is the capital of Colorado?
    A. Raleigh
    B. Harrisburg
    C. Denver
    D. Lincoln

# --snip-- 

相应的capitalsquiz_answers1.txt文本文件将看起来像这样:

1\. D
2\. C

# --snip-- 

手动随机排序问题集和相应的答案键将花费数小时,但只要有一点编程知识,你就可以自动化这项无聊的任务,不仅限于州首府知识问答,还可以用于任何多项选择题。

概述

操作系统将文件组织成文件夹(也称为目录),并使用路径来描述它们的位置。每台计算机上运行的每个程序都有一个当前工作目录,这使得你可以相对于当前位置指定文件路径,而不是输入完整的(或绝对)路径。pathlibos.path模块有许多用于操作文件路径的函数。

你的程序也可以直接与文本文件的内容进行交互。open()函数可以打开这些文件,以一个大的字符串(使用read()方法)或字符串列表(使用readlines()方法)的形式读取其内容。open()函数还可以以写入或追加模式打开文件,分别用于创建新的文本文件或向现有文本文件中添加内容。

在前面的章节中,你使用剪贴板作为将大量文本输入程序的一种方式,而不是直接输入。现在你可以让程序从硬盘读取文件,这是一个很大的改进,因为文件比剪贴板更稳定。

在下一章中,你将学习如何通过复制、删除、重命名、移动等方式处理文件本身。

实践问题

1.  相对路径相对于什么?

2.  绝对路径从哪里开始?

3.  在 Windows 上,Path('C:/Users') / 'Al' 的值是多少?

4.  在 Windows 上,'C:/Users' / 'Al' 的值是多少?

5.  os.getcwd()os.chdir() 函数的作用是什么?

6.  ... 文件夹是什么?

7.  在 C:\bacon\eggs\spam.txt 中,哪部分是目录名,哪部分是基本名?

8.  你可以向 open() 函数传递哪三个“模式”参数来处理纯文本文件?

9.  如果以写入模式打开现有文件会发生什么?

10.  read()readlines() 方法之间的区别是什么?

11.  货架值类似于哪种数据结构?

实践程序

为了练习,设计和编写以下程序。

Mad Libs

创建一个 Mad Libs 程序,该程序读取文本文件,并允许用户在文本文件中任何出现单词 ADJECTIVENOUNADVERBVERB 的位置添加自己的文本。例如,一个文本文件可能看起来像这样:

The ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was
unaffected by these events. 

程序将找到这些匹配项,并提示用户进行替换:

Enter an adjective:
silly
Enter a noun:
chandelier
Enter a verb:
screamed
Enter a noun:
pickup truck

然后将创建以下文本文件:

The silly panda walked to the chandelier and then screamed. A nearby
pickup truck was unaffected by these events. 

程序应将结果打印到屏幕上,并保存到新的文本文件中。

正则表达式搜索

编写一个程序,该程序打开文件夹中所有的 .txt 文件,并搜索任何与用户提供的正则表达式匹配的行,然后将结果打印到屏幕上。

Mad Libs

创建一个 Mad Libs 程序,该程序读取文本文件,并允许用户在文本文件中任何出现单词 ADJECTIVENOUNADVERBVERB 的位置添加自己的文本。例如,一个文本文件可能看起来像这样:

The ADJECTIVE panda walked to the NOUN and then VERB. A nearby NOUN was
unaffected by these events. 

程序将找到这些匹配项,并提示用户进行替换。

Enter an adjective:
silly
Enter a noun:
chandelier
Enter a verb:
screamed
Enter a noun:
pickup truck

然后将创建以下文本文件:

The silly panda walked to the chandelier and then screamed. A nearby
pickup truck was unaffected by these events. 

程序应将结果打印到屏幕上,并保存到新的文本文件中。

正则表达式搜索

编写一个程序,该程序打开文件夹中所有的 .txt 文件,并搜索任何与用户提供的正则表达式匹配的行,然后将结果打印到屏幕上。

posted @ 2026-02-06 10:27  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报