Python文件处理与错误处理:如何提高程序的适用性、可用性和稳定性
在本章里,你要学习如何处理文件,以使程序能快速分析大量数据;你要学习错误处理方面的知识,避免程序在遭遇意外情形时崩溃;你要学习异常,它们是 Python 所创建的特殊对象,用于对程序运行时出现的错误进行管理;你还将学习模块 json,它能让你保存用户数据,防止在程序停止运行后数据丢失。

学习处理文件和保存数据能让你的程序更易于使用:用户能够选择输入的数据类型以及输入的时间;用户使用完你的程序进行一些工作后,可以关闭程序,之后再继续进行操作。学习处理异常有助于应对文件不存在的情况以及其他可能致使程序崩溃的问题。你在本章所学的技能能够提升程序的适用性、可用性和稳定性。
从文件中读取数据
文本文件能存储海量数据,如天气数据、交通数据、社会经济数据、文学作品等,其数据量令人难以置信。每当需要对存储在文件中的信息进行分析或修改时,读取文件就会很有帮助,这对数据分析应用程序而言尤为突出。例如,你可以编写一个程序,该程序能够读取一个文本文件的内容,重新调整这些数据的格式并将其写入文件,以使浏览器能够显示这些内容。
要使用文本文件中的信息,首先得把信息读取到内存里。要做到这一点,你既可以一次性把文件的全部内容都读取进来,也可以每次只读取一行,然后逐步进行读取。
读取整个文件
要读取文件,就需要有一个包含几行文本的文件。首先来创建一个文件,这个文件里包含着精确到小数点后 30 位的圆周率值,并且在小数点后每 10 位的地方都进行换行。
3.1415926535
8979323846
2643383279
若要动手尝试后续示例,可在编辑器中输入这些数据行,接着把文件保存为 pi_digits.txt 。同时,也可以从本书的配套网站( )下载该文件,之后将该文件保存到本章程序所在的目录中。
下面的程序打开并读取这个文件,再将其内容显示到屏幕上:
打开名为“pi_digits.txt”的文件,将其作为 file_object 进行操作。
file_object 读取的内容为 contents
print(contents)
在这个程序里,第 1 行代码承担了很多工作。我们首先来看函数 open()。如果要以任何形式使用文件,哪怕只是打印其内容,都必须先将文件打开,只有这样才能对其进行访问。函数 open() 会接收一个参数,即要打开的文件的名称。Python 会在当前正在执行的文件所在的目录中去查找指定的文件。在这个示例里,现在运行的是 file_reader.py 。所以,Python 会在 file_reader.py 所在的那个目录里去查找 pi_digits.txt 。函数 open() 会返回一个能够表示文件的对象。在这里,open('pi_digits.txt') 会返回一个用于表示文件 pi_digits.txt 的对象;Python 会把这个对象存储在一个变量中,而我们将在后面使用这个变量。
在不再需要访问文件后,需将其关闭。在这个程序里,我们调用了 open() 却未调用 close() ;也可以分别调用 open() 和 close() 来打开和关闭文件,然而若程序有 bug 致使 close() 语句未执行,文件就不会被关闭。这看似是件小事。如果没有妥善关闭文件,就可能会引发数据丢失或受损的情况。要是在程序中过早地调用 close() ,之后再想用这个文件时就会发现它已经关闭了,也就无法访问了,这样会导致更多的错误。在有些情况下不容易轻松确定关闭文件的恰当时机,然而借助使用前面所展示的结构,能够让 Python 来确定:你仅仅需要打开文件,并且在有需要的时候使用它,Python 自己就会在恰当的时刻自动把它关闭。
有了文件对象表示 pi_digits.txt 后,我们在程序的第 2 行使用方法 read() 。然后,这个方法会读取这个文件的全部内容,并将其作为一个长长的字符串存储在变量 contents 中。这样,通过打印 contents 的值,就能够将这个文本文件的全部内容显示出来。
3.1415926535
8979323846
2643383279
该输出与原始文件相比,唯一的不同之处在于末尾多了一个空行。为何会多出这个空行呢?是因为 read() 在到达文件末尾时会返回一个空字符串,而将这个空字符串显示出来就形成了一个空行。若要删除多出来的空行,可以在 print 语句中使用 rstrip() 。
with open('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents.rstrip())
本书之前讲过,Python 的方法 rstrip()能够把字符串末尾的空白给删除掉。此刻,输出的内容和原始文件的内容是完全一样的。
3.1415926535
8979323846
2643383279
文件路径
当你把像 pi_digits.txt 这样简单的文件名传递给函数 open() 时,Python 会在当前正在执行的文件(也就是.py 程序文件)所处的目录里去查找文件。
根据你组织文件的方式,有时需打开不在程序文件所属目录里的文件。比如,你或许把程序文件存放在文件夹 python_work 中,并且在该文件夹 python_work 里,有一个叫 text_files 的文件夹,此文件夹是用来存储程序文件操作的文本文件的。文件夹 text_files 包含在文件夹 python_work 里,但是仅仅向 open() 传递位于该文件夹中的文件的名称是不行的。因为 Python 只会在文件夹 python_work 中进行查找,而不会在它的子文件夹 text_files 中查找。要让 Python 打开不在程序文件所在目录的文件,就需要给出文件路径,这样 Python 就能到系统的特定位置去进行查找。
文件夹 text_files 在文件夹 python_work 中,所以能够使用相对文件路径来打开该文件夹里的文件。相对文件路径能让 Python 前往指定位置进行查找,这个指定位置是相对于当前正在运行的程序所在的目录而言的。在 Linux 和 OS X 系统中,你可以按照如下方式编写代码:
打开文件“text_files/filename.txt”,并将其赋值给变量 file_object 。
这行代码能使 Python 前往文件夹 python_work 里的文件夹 text_files 去寻觅指定的.txt 文件。在 Windows 系统里,在文件路径方面会使用反斜杠(\)而非斜杠(/)。
打开名为'text_files\filename.txt'的文件,将其赋值给 file_object 这个变量。
你可以把文件在计算机中的准确位置告知 Python 。这样一来,就无需在意当前运行的程序存储于何处了。这被称作绝对文件路径 。当相对路径无法使用时,就可以使用绝对路径 。如果 text_files 不在文件夹 python_work 里,而是在文件夹 other_files 里,那么向 open() 传递路径'text_files/ filename.txt' 是不行的,因为 Python 只会在文件夹 python_work 中查找该位置。你希望 Python 到特定的地方去查找,为了明确指出这一点,你需要提供完整的路径。
绝对路径一般比相对路径要长一些。所以把它存储在一个变量里,接着把这个变量传递给 open() 是会有帮助的。在 Linux 和 OS X 中,绝对路径类似如下这种情况:
file_path 是在 /home/ehmatthes/other_files/text_files/ 路径下的 filename.txt 文件的路径
打开文件对象,使用 file_path 作为文件名。具体来说,通过执行 with open(file_path) as file_object 这一操作,实现了打开指定文件路径的文件,并将其关联到 file_object 这个变量上,以便后续对文件进行读取、写入等操作。
而在Windows系统中,它们类似于下面这样:
file_path 为 'C:\Users\ehmatthes\other_files\text_files\filename.txt'
with open(file_path) as file_object:
使用绝对路径就能读取系统任何地方的文件。目前,最简单的做法有两种:一是把数据文件存储在程序文件所在的目录;二是把数据文件存储在程序文件所在目录下的一个文件夹(比如 text_files)中。
Windows 系统有时能正确解读文件路径中的斜杠。若你使用的是 Windows 系统,而结果与预期不符,需确保在文件路径中使用反斜杠。
逐行读取
读取文件时,经常需要对其中的每一行进行检查:或许要在文件里找寻特定的信息,又或许要以某种形式去修改文件中的文本。比如,可能要依次遍历一个存有天气数据的文件,并且利用天气描述中包含“sunny”字样的行。在新闻报道里,可能会去查找包含特定标签的行,然后按照特定的格式对其进行设置。
要以每次一行的方式检查文件,可对文件对象使用for 循环:
filename = 'pi_digits.txt'
打开(filename)这个文件,将其作为 file_object 进行操作。
for line in file_object:
print(line)
在某个地方,我们把将要读取的文件的名称存放在变量 filename 里,这在使用文件时是一种较为常见的操作。因为变量 filename 所代表的并不是真实的文件,而仅仅是一个能让 Python 知晓到何处去查找文件的字符串,所以可以很方便地将'pi_digits.txt'替换为你需要使用的另一个文件的名称。调用 open() 之后,把一个能表示文件及其内容的对象存到了变量 file_object 里。这里还使用了关键字 with ,由 Python 来负责妥善地打开和关闭文件。要查看文件的内容,我们通过对文件对象进行循环,从而遍历文件中的每一行。
我们打印每一行时,发现空白行更多了:
3.1415926535
8979323846
2643383279
为什么会出现这些空白行呢?在这个文件里,每行的末尾都存在一个看不见的换行符,并且 print 语句也会添加一个换行符,所以每行的末尾就有两个换行符,一个是来自文件的,另一个是来自 print 语句的。要去除这些多余的空白行,就可以在 print 语句中使用 rstrip()。
filename = 'pi_digits.txt'
打开(filename)后,将其作为 file_object 进行操作。
对于每一行在文件对象中:
打印(line 去除右侧空白字符后的内容)
现在,输出又与文件内容完全相同了:
3.1415926535
8979323846
2643383279
创建一个包含文件各行内容的列表
当使用关键字 with 时,open()所返回的文件对象仅在 with 代码块内是可用的。倘若想要在 with 代码块之外访问文件的内容,那么可以在 with 代码块内把文件的各行存储在一个列表里,接着在 with 代码块之外使用这个列表。你既可以即刻对文件的各个部分进行处理,也可以将处理时间推迟到程序的后续部分。
下面的示例在 with 代码块中把文件 pi_digits.txt 的各行存储到一个列表里,接着在 with 代码块之外打印这些行。
filename = 'pi_digits.txt'
打开文件 filename ,将其赋值给 file_object 。
file_object 读取的内容以行的形式存储在 lines 中
for line in lines:
print(line.rstrip())
使用 readlines() 方法可以从文件中读取每一行,并将其存储在一个列表里;然后,这个列表被存储到变量 lines 中;在 with 代码块之外,我们还能够使用这个变量。在特定的地方,我们通过一个简单的 for 循环来打印 lines 中的每一行。列表 lines 的每个元素都对应着文件中的一行,所以输出的内容和文件内容是完全一样的。
使用文件的内容
把文件读取进内存后,就能够以各种方式运用这些数据啦。接下来以简单的形式来使用圆周率的值。首先,我们要创建一个字符串,这个字符串要包含文件里存储的所有数字,并且不能有任何空格。
filename = 'pi_digits.txt'
with open(filename) as file_object:
文件对象读取到的内容被按行分割成了列表,这些行就是 lines 。
pi_string = ''
for line in lines:
pi_string += line.rstrip()
print(pi_string)
print(len(pi_string))
如同之前的示例那般,我们首先开启文件,接着把文件里的所有行都储存到一个列表当中。在此处,我们构建了一个变量——pi_string,用以储存圆周率的值。随后,我们借助一个循环把各行都添加到 pi_string 里,并且去除每行末尾的换行符(参考)。在该处,我们将打印这个字符串以及它的长度:
3.1415926535 是一个小数,它的数位依次为 8979323846 2643383279 。这个小数有着特定的数值和排列顺序,每个数位都有其特定的意义和价值。
36
在变量 pi_string 所存储的字符串里,包含原本处在每行左边的空格。要删除这些空格的话,可以使用 strip() 而不是 rstrip() 。
filename = 'pi_30_digits.txt'
with open(filename) as file_object:
file_object 读取了一些行,这些行被存储在 lines 中。
pi_string = ''
for line in lines:
pi_string += line.strip()
print(pi_string)
print(len(pi_string))
这样,我们获得了一个字符串。这个字符串包含精确到 30 位小数的圆周率值。该字符串长 32 字符,因为它还包含整数部分的 3 以及小数点。
3.141592653589793238462643383279 是一个具有特定数值的数字序列,它包含了许多小数位,展现出了其独特的数学特征。这个数字在数学领域中有着重要的地位,被广泛应用于各种计算和研究中。它的每一位数字都有着特定的意义和价值,共同构成了这个复杂而精确的数值。
32
在读取文本文件时,Python 会把其中的所有文本都当作字符串来解读。如果你读取的是数字,并且想要将其当作数值来使用,那么就必须使用函数 int() 把它转换为整数,或者使用函数 float() 把它转换为浮点数。
包含一百万位的大型文件
我们之前分析的是一个仅有三行的文本文件,然而这些代码示例能够处理大很多的文件。若我们拥有一个文本文件,该文件中包含精确到小数点后 1000000 位而非 30 位的圆周率值,那么也可以创建一个包含所有这些数字的字符串。在此情况下,我们不需要对之前的程序进行任何改动,只需把这个文件传递给它就行。在这里,我们将打印的精度控制在小数点后 50 位。这样做是为了避免终端因要显示全部 1000000 位而不停地翻滚。
文件名是“pi_million_digits.txt”。
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(pi_string[:52] + "...")
print(len(pi_string))
输出显示,我们创建的字符串包含了精确到小数点后 1000000 位的圆周率值。
3.14159265358979323846264338327950288419716939937510 等数字依次排列。这些数字有着特定的顺序和组合。它们构成了一个独特的序列。这个序列在数学等领域有着重要的意义和应用。
1000002
Python 对于你可处理的数据量没有限制。只要系统的内存足够多,你就可以处理任意多的数据。
要运行这个程序以及后面的众多示例,你得从某处下载相关的资源。
圆周率值中包含你的生日吗
我一直渴望知晓自己的生日是否被包含在圆周率值里。接下来扩展刚才编写的程序,目的是确定某个人的生日是否存在于圆周率值的前 1000000 位当中。为此,可以把生日用由数字组成的字符串来表示,然后检查这个字符串是否包含在 pi_string 之中。
filename = 'pi_million_digits.txt'
with open(filename) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.rstrip()
输入你的生日,格式为 mmddyy,将其赋值给变量 birthday 。
if birthday in pi_string:
打印("你的生日出现在圆周率的前一百万位数字中!")
else:
你的生日未出现在圆周率的前一百万位数字中。
在某个地方,我们会提示用户输入他们的生日。在后续的某个地方,我们会检查这个字符串是否包含在 pi_string 里。运行一下这个程序。
请以 mmddyy 的形式输入你的出生日期:120372
我的生日出现在了圆周率值中。读取文件内容后,能够以你所能想到的各种方式对其进行分析。