读取文件

文本文件中提供了大量的数据。 文本文件可以包含天气数据、交通数据、社会经济数据、文学作品等。 从文件中读取在数据分析应用程序中特别有用,但它也适用于您想要分析或修改存储在文件中的信息的任何情况。 例如,您可以编写一个程序来读取文本文件的内容,然后使用允许浏览器显示的格式重写该文件。

当您想要使用文本文件中的信息时,第一步是将文件读入内存。 然后,您可以一次处理文件的所有内容或逐行处理内容。

读取文件的全部内容

首先,我们需要一个包含几行文本的文件。 让我们从一个包含 pi 到 30 位小数的文件开始,每行 10 位小数:

pi_digits.txt
3.1415926535
  8979323846
  2643383279

要亲自尝试以下示例,您可以在编辑器中输入这些行并将文件保存为 pi_digits.txt,或者您可以通过 https://ehmatthes.github.io/pcc_3e 从本书的资源中下载该文件。 将该文件保存在您将存储本章程序的同一目录中。

这是一个打开这个文件、读取它并将文件内容打印到屏幕的程序:

from pathlib import Path


path = Path('pi_digits.txt') (1)
contents = path.read_text() (2)
print(contents)

要处理文件的内容,我们需要告诉 Python 文件的路径。 路径是系统上文件或文件夹的确切位置。 Python 提供了一个名为 pathlib 的模块,无论您或您的程序的用户使用哪种操作系统,它都可以更轻松地处理文件和目录。 像这样提供特定功能的模块通常称为库,因此得名 pathlib。

我们首先从 pathlib 导入 Path 类。 使用指向文件的 Path 对象可以做很多事情。 例如,您可以在使用文件之前检查文件是否存在、读取文件内容或将新数据写入文件。 在这里,我们构建一个代表文件 pi_digits.txt 的 Path 对象,我们将其分配给变量路径。 由于此文件保存在与我们正在编写的 .py 文件相同的目录中,因此文件名是 Path 访问该文件所需的全部内容。

VS Code 在最近打开的文件夹中查找文件。 如果你使用的是 VS Code,首先打开你存储本章程序的文件夹。 例如,如果您将程序文件保存在名为 chapter_10 的文件夹中,请按 CTRL-O(在 macOS 上为 ⌘-O),然后打开该文件夹。

一旦我们有了表示 pi_digits.txt 的 Path 对象,我们就可以使用 read_text() 方法读取文件的全部内容。 文件的内容作为单个字符串返回,我们将其分配给变量 contents 。 当我们打印 contents 值时,我们会看到文本文件的全部内容:

pi_digits.txt
3.1415926535
  8979323846
  2643383279

此输出与原始文件之间的唯一区别是输出末尾的额外空行。 出现空行是因为 read_text() 到达文件末尾时返回空字符串; 这个空字符串显示为空行。

我们可以通过在内容字符串上使用 rstrip() 来删除多余的空行:

from pathlib import Path


path = Path('pi_digits.txt')
contents = path.read_text()
contents = contents.rstrip()
print(contents)

回想一下第 2 章,Python 的 rstrip() 方法从字符串的右侧移除或剥离任何空白字符。 现在输出与原始文件的内容完全匹配:

pi_digits.txt
3.1415926535
  8979323846
  2643383279

我们可以在读取文件内容时去除尾随的换行符,方法是在调用 read_text() 后立即应用 rstrip() 方法:

contents = path.read_text().rstrip()

这一行告诉 Python 在我们正在处理的文件上调用 read_text() 方法。 然后它将 rstrip() 方法应用于 read_text() 返回的字符串。 然后将清理后的字符串分配给变量 contents 。 这种方法称为方法链,您会在编程中经常看到它。

相对文件路径和绝对文件路径

当您将一个简单的文件名(如 pi_digits.txt)传递给 Path 时,Python 会查找当前正在执行的文件(即您的 .py 程序文件)的存储目录。

有时,根据您组织工作的方式,您要打开的文件不会与您的程序文件位于同一目录中。 例如,您可以将程序文件存储在名为 python_work 的文件夹中; 在 python_work 中,您可能有另一个名为 text_files 的文件夹来区分您的程序文件和它们正在处理的文本文件。 尽管 text_files 在 python_work 中,但仅将 text_files 中的文件名传递给 Path 是行不通的,因为 Python 只会在 python_work 中查找并停在那里; 它不会继续查看 text_files。 要让 Python 从存储程序文件的目录以外的目录打开文件,您需要提供正确的路径。

在编程中指定路径主要有两种方式。 相对文件路径告诉 Python 查找相对于当前运行的程序文件存储目录的给定位置。 由于 text_files 在 python_work 中,我们需要构建一个以目录 text_files 开始,以文件名结束的路径。 以下是构建此路径的方法:

path = Path('text_files/filename.txt')

您还可以告诉 Python 文件在您计算机上的确切位置,而不管正在执行的程序存储在何处。 这称为绝对文件路径。 如果相对路径不起作用,您可以使用绝对路径。 例如,如果您将 text_files 放在 python_work 以外的某个文件夹中,那么仅将路径“text_files/filename.txt”传递给 Path 将不起作用,因为 Python 只会在 python_work 中查找该位置。 您需要写出一个绝对路径来阐明您希望 Python 查找的位置。

绝对路径通常比相对路径长,因为它们从系统的根文件夹开始:

path = Path('/home/eric/data_files/text_files/filename.txt')

使用绝对路径,您可以从系统上的任何位置读取文件。 目前,最简单的方法是将文件存储在与程序文件相同的目录中,或者存储在存储程序文件的目录中的文件夹(例如 text_files)中。

Windows 系统在显示文件路径时使用反斜杠 (\) 而不是正斜杠 (/),但您应该在代码中使用正斜杠,即使在 Windows 上也是如此。 当 pathlib 库与您的系统或任何用户的系统交互时,它会自动使用正确的路径表示。

访问文件中的各行

在处理文件时,您通常会希望检查文件的每一行。 您可能正在文件中查找某些信息,或者您可能希望以某种方式修改文件中的文本。 例如,您可能想要通读一个天气数据文件,并处理在当天天气描述中包含单词 sunny 的任何行。 在新闻报道中,您可能会查找带有标记 <headline> 的任何行,并使用特定类型的格式重写该行。

您可以使用 splitlines() 方法将一个长字符串转换为一组行,然后使用 for 循环检查文件中的每一行,一次一行:

file_reader.py
from pathlib import Path


path = Path('pi_digits.txt')
contents = path.read_text() (1)

lines = contents.splitlines() (2)
for line in lines:
  print(line)
1 我们从读取文件的全部内容开始,就像我们之前所做的一样。 如果您打算处理文件中的各个行,则在读取文件时不需要去除任何空格。
2 splitlines() 方法返回文件中所有行的列表,我们将这个列表赋值给变量 lines。 然后我们遍历这些行并打印每一行:
pi_digits.txt
3.1415926535
  8979323846
  2643383279

由于我们没有修改任何行,因此输出与原始文本文件完全匹配。

使用文件的内容

将文件内容读入内存后,您可以对这些数据做任何想做的事情,所以让我们简单地探讨一下 pi 的数字。 首先,我们将尝试构建一个包含文件中所有数字且没有空格的字符串:

from pathlib import Path


path = Path('pi_digits.txt')
contents = path.read_text()

lines = contents.splitlines()
pi_string = ''
for line in lines: (1)
    pi_string += line
    
print(pi_string)
print(len(pi_string))
1 我们首先读取文件并将每一行数字存储在一个列表中,就像我们在前面的示例中所做的那样。 然后我们创建一个变量 pi_string 来保存 pi 的数字。 我们编写一个循环,将每一行数字添加到 pi_string 。 我们打印这个字符串,并显示这个字符串有多长:
3.1415926535 8979323846 2643383279
36

变量 pi_string 包含每行数字左侧的空格,但我们可以通过在每行中使用 lstrip() 来摆脱它:

from pathlib import Path


path = Path('pi_digits.txt')
contents = path.read_text()

lines = contents.splitlines()
pi_string = ''
for line in lines:
    pi_string += line.lstrip()
    
print(pi_string)
print(len(pi_string))

现在我们有一个包含 pi 到小数点后 30 位的字符串。 该字符串的长度为 32 个字符,因为它还包括前导 3 和一个小数点:

3.141592653589793238462643383279
32

当 Python 从文本文件中读取时,它将文件中的所有文本解释为字符串。 如果您读入一个数字并想在数字上下文中使用该值,则必须使用 int() 函数将其转换为整数或使用 float() 函数将其转换为浮点数。

包含100万位的大型文件

到目前为止,我们一直专注于分析仅包含三行的文本文件,但这些示例中的代码在更大的文件上同样适用。 如果我们从一个包含 pi 到 1,000,000 位小数而不是仅仅 30 位的文本文件开始,我们可以创建一个包含所有这些数字的字符串。 我们根本不需要改变我们的程序,除了给它传递一个不同的文件。 我们还将只打印前 50 位小数,因此我们不必在终端中观看一百万位数字:

from pathlib import Path


path = Path('pi_million_digits.txt')
contents = path.read_text()

lines = contents.splitlines()
pi_string = ''
for line in lines:
    pi_string += line.lstrip()

print(f"{pi_string[:52]}...")
print(len(pi_string))

输出显示我们确实有一个包含 pi 到小数点后 1,000,000 位的字符串:

3.14159265358979323846264338327950288419716939937510...
1000002

Python 对您可以处理的数据量没有固有的限制; 您可以处理系统内存所能处理的尽可能多的数据。

要运行此程序(以及随后的许多示例),您需要从 https://ehmatthes.github.io/pcc_3e 下载可用资源。

圆周率值中包含你的生日吗

我一直很想知道我的生日是否出现在 pi 的数字中。 让我们使用刚刚编写的程序来查明某人的生日是否出现在 pi 的前百万位中的任何位置。 我们可以通过将每个生日表示为一串数字并查看该字符串是否出现在 pi_string 中的任何地方来做到这一点:

from pathlib import Path


path = Path('pi_million_digits.txt')
contents = path.read_text()

lines = contents.splitlines()
pi_string = ''
for line in lines:
    pi_string += line.lstrip()

birthday = input("Enter your birthday, in the form mmddyy: ")
if birthday in pi_string:
    print("Your birthday appears in the first million digits of pi!")
else:
    print("Your birthday does not appear in the first million digits of pi.")

我们首先提示用户的生日,然后检查该字符串是否在 pi_string 中。 让我们试试看:

Enter your birthdate, in the form mmddyy: 120372
Your birthday appears in the first million digits of pi!

我的生日确实出现在 pi 的数字中! 读取文件后,您可以用任何您能想到的方式分析其内容。

自己试试
10-1.Learning Python

在你的文本编辑器中打开一个空白文件,写几行总结你到目前为止所学的 Python 知识。 每行以 In Python you can 开头。 . . . 将文件另存为 learning_python.txt,保存在与本章练习相同的目录中。 编写一个程序来读取文件并打印您所写的内容两次:一次通过读取整个文件来打印内容,一次通过将行存储在列表中然后遍历每一行来打印内容。

10-2.Learning C

可以使用 replace() 方法将字符串中的任何单词替换为不同的单词。 下面是一个快速示例,展示了如何将句子中的“狗”替换为“猫”:

>>> message = "I really like dogs."
>>> message.replace('dog', 'cat')
'I really like cats.'

读取您刚刚创建的文件 learning_python.txt 中的每一行,并将单词 Python 替换为另一种语言的名称,例如 C。 将每个修改过的行打印到屏幕上。

10-3.Simpler Code

本节中的程序 file_reader.py 使用临时变量 lines 来显示 splitlines() 的工作原理。 您可以跳过临时变量并直接遍历 splitlines() 返回的列表:

for line in contents.splitlines():

从本节中的每个程序中删除临时变量,使它们更简洁。