(请先看这篇文章:https://blog.csdn.net/GenuineMonster/article/details/104495419)
如果看完这篇博文,你的问题还是没有解决,那么请关注我的公众号,后台发消息给我吧,当天回复!
学习文件处理,你将能够快速的分析大量的数据;学习错误处理,你将会编写在意外情形下也不崩溃的程序;学习异常,你将会管理程序运行时出现的错误;你还将学习json,从而能够保存用户数据,以免在程序停止运行后丢失。异常一般来源于破坏程序的恶意企图以及错误数据。举个例子:假设QQ的密码不能包含字母,并且后台程序在判别时也忽略了对字母的识别(没有相关代码)。那么此时,如果你在设置密码时,输了字母,后台程序接受到了,但不知道如何处理这个“不按常理出牌”的密码,此时程序就又可能瘫痪,进而出现错误。“错误及异常”和其他面向对象语言中的一样,就是Java中俗称的“异常抛出机制”。学习这些机制的主要目的是提高程序的适用性、可用性和稳定性。
一、从文件中读取数据
文本文件中可存储的数据量多得难以置信,每当需要分析或修改存储在文件中的信息时,读取文件是很有必要的。要使用文本文件中的信息,首先需要将信息读取到内存中。为此,可以一次性读取文件的全部内容,也可以以每一次的方式逐步读取。
1.1 读取整个文件
我们先来创建一个包含几行文本的文件,名称为pi_digits.txt。内容如下截图:
接下来我们编写代码,读取这个txt文件中的内容,并输出:
with open('pi_digits.txt') as file_object:contents =file_object.read()print(contents)
要知道,无论我们以任何方式使用文件——哪怕仅仅是打印其内容,都得先打开文件,这样才能访问它。
A、函数open()接受一个参数:要打开的文件的名称。Python在当前执行的程序文件所在的目录中查找指定的文件,找到后,执行open()函数,然后返回一个表示文件的对象,并存储在file_object中。
B、关键字with的作用是在不再需要访问文件后将其关闭。在这个过程中,我们只调用了open(),没调用close()。你也可以调用open()和close()来打开和关闭文件,但这样做时,如果程序存在bug,导致close()语句未执行,文件将不会关闭。看似微不足道,但未妥善地关闭文件可能会导致数据丢失或受损。如果在程序中过早的调用close(),你会发现需要使用文件时它已关闭(无法访问),这会导致更多地错误。(并非在任何情况下都能轻松确定关闭文件的恰当时机)但我们可以通过使用with,让Python去确定:我们只管打开文件,并在需要时使用它,Python自会在合适地时候自动将其关闭。
C、有了表示pi_digits.txt的文件对象后,我们使用read()方法读取这个文件的全部内容,并将其作为一个长长的字符串存储在变量contents中。这样我们就可以打印全部的内容了。
细心的人已经发现问题了,为什么输出后有这么多空行?这是因为read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除末尾的空行,可在print语句中使用rstrip():(我实验了,貌似没什么用啊)
with open('pi_digits.txt') as file_object:contents =file_object.read()print(contents.rstrip())
1.2 文件路径
如果要Python打开不与程序文件位于同一目录中的文件,需要提供文件路径,它让Python到系统的特定位置去查找。文件路径分为绝对路径和相对路径。
A、相对路径:文件所处的位置是相对于当前运行的程序文件所属目录而言的路径,举个例子:(注意使用的是反斜杠)
C:\Users\Jeffery_PC\Desktop\code # py文件的目录
C:\Users\Jeffery_PC\Desktop\code\123 # 目标文件目录目标文件目录和py文件目录的前面一部分都是一样的,所以省略不写(相当于以py文件的目录为参考)
所以目标文件的相对路径最终表示为:123\pi_digits.txt
我们可以实验一下,看看能否读取文件成功:代码如下:
with open('123\pi_digits.txt') as file_object:contents =file_object.read()print(contents.rstrip())
B、绝对路径:目标文件在计算机中的准确位置就是绝对路径,举个例子:
C:\Users\xxxxxx\Desktop\code\123\pi_digits.txt # xxxxxx是我的信息,这里隐写了
在相对路径行不通时,可使用绝对路径。绝对路径通常比相对路径更长,因此将其存储在一个变量中,再将这个变量传递给open()会有所帮助。就目前而言,最简单的做法是:要么将数据文件存储在程序文件所在的目录,要么将其存储在程序文件所在目录下的一个文件夹中。
1.3 逐行读取
读取文件时,常常需要检查其中的每一行:可能要在文件中查找特定的信息,或者要以某种方式修改文件中的文本。要以每次一行的方式检查文件,可对文件对象使用for循环:
filename = 'pi_digits.txt'
with open(filename) as file_object:for line in file_object:print(line)
这段代码与之前的代码类似,就不解释了。出现空白行的原因是:读取文档每行的末尾都有一个看不见的换行符,而print语句也会加上一个换行符,因此每行末尾都有两个换行符:一个来自文件,一个来自print语句。要消除这些多余的空白行,可在print语句末尾使用rstrip():
filename = 'pi_digits.txt'
with open(filename) as file_object:for line in file_object:print(line.rstrip())
1.4 创建一个包含文件各行内容的列表
使用关键字with时,open()返回的文件对象只在with代码块内可用。如果要在with代码块外访问文件的内容,可在with代码块内将文件的各行存储在一个列表里,并在with代码块外使用该列表:你可以立即处理文件的各个部分,也可以推迟到程序后面再处理。
例代码:
filename = 'pi_digits.txt'
with open(filename) as file_object:lines = file_object.readlines() # 按行读取,并存储在列表中for line in lines:print(line.rstrip()) # 使用for循环来打印每一行的内容
1.5 使用从文件中读取的内容
我们将会读取文件内容,并组成一个新的字符串(组成新的字符串就是我们的“使用”)(将文件读取到内存中后,就可以以任何方式使用这些数据了)
filename = 'pi_digits.txt'
with open(filename) as file_object:lines = file_object.readlines() # 按行读取,并存储在列表中pi_string = ''
for line in lines:pi_string += line.rstrip()print(pi_string)
print(len(pi_string))
看出这个字符串中间还有空格,所以可以使用strip()函数不是rstrip():
filename = 'pi_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) # 精确到30位小数的圆周率值
print(len(pi_string)) # 输出字符串的长度
注意:读取文本文件时,Python将其中的所有文本都解读为字符串。如果读取的是数字,并要将其作为数值使用,就必须使用函数int()将其转换为整数,或使用函数float()将其转换为浮点数。
1.6 处理大体量文件
即将要处理的文件内部包含圆周率小数点后一百万位,我们将其打印出来。
filename = '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] + "...") # 打印到小数点后50位,因为整数和小数点也需要2位
print(len(pi_string))
对于你可处理的数据量,Python没有任何限制;只要系统的内存足够多,你想处理多少数据都可以。
1.7 牛刀小试
如果大家关注过北大数院微信公众号,那么对其中一个功能“在圆周率中寻找你的生日”就很熟悉了,这里我们自己也实现一下,检测某个人的生日是否在圆周率的100万当中。逻辑是:将自己的生日字符串化,再检查这个字符串是否包含在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.strip()birthday = input ("Enter your birthday, in the form mmddyy: ") # input()函数需要用户输入信息
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!")
作者的生日的确出现在Π的前100万位中。
二 将数据写入文件
保存数据的最简单的方式之一是将其写入到文件中。通过将输出写入文件,即便关闭包含程序输出的终端窗口,这些输出也依然存在。
2.1 写入空文件
要将文本写入文件,你在调用open()时需要提供另一个实参,告诉Python你要写入打开的文件。接下来以一段具体代码演示一下:
filename = 'programming.txt'
# 如果你写入的文件不存在,open()将自动创建
with open(filename, 'w') as file_object: # 为open()函数提供两个实参:第一个指明要打开的文件名称# 第二个实参指明:我们将以“写入模式”打开这个文件# ‘r’:读取模式# ‘w’:写入模式# ‘a’:附加模式# ‘r+’:读取写入模式# 如果省略了模式实参,Python将以默认的只读模式打开文件 file_object.write("I love programming!")
成功写入!这里需要注意的是:
A、以写入(‘W’)模式打开文件时,千万要小心,因为如果指定的文件已经存在,Python将在返回文件对象前清空该文件。
B、Python只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数str()将其转换为字符串格式。
2.2 写入多行
这里只需要注意一点,函数write()不会在你写入的文本末尾自动添加换行符,需要你自己添加。如果还需要其他格式,可以使用“空格”、“制表符”、“空行”来设置文本格式:
filename = 'programming.txt'with open(filename, 'w') as file_object:# 为open()函数提供两个实参:第一个指明要打开的文件名称# 第二个实参指明:我们将以“写入模式”打开这个文件# ‘r’:读取模式# ‘w’:写入模式# ‘a’:附加模式# ‘r+’:读取写入模式# 如果省略了模式实参,Python将以默认的只读模式打开文件file_object.write("I love programming!\n")file_object.write("I love creating new games.\n")
2.3 附加到文件(追加到文件)
如果你要给文件添加内容,而不是覆盖原有的内容,可以以附加模式打开文件。如果你以附加模式打开文件时,Python不会在返回文件对象前清空文件,而你写入到文件的行都将添加到文件末尾。如果指定的文件不存在,Python将会为你创建一个空文件。
filename = 'programming.txt'with open(filename, 'a') as file_object:file_object.write("I also love finding meaning in large datasets. \n")file_object.write("I love creating apps that can run in a browser.\n")
三 异常
Python使用被称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继续运行;如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告。
异常是使用try-except代码块处理的。try-except代码块让Python执行指定的操作,同时告诉Python发生异常时怎么办。使用了try-except代码块时,即便出现异常,程序也将继续运行:显示你编写的友好的错误消息,而不是令用户迷惑的traceback。
3.1 处理ZeroDivisionError异常
举个简单的例子:在数学中,0不可以作除数。那如果在Python中出现这样的问题呢?
print(5/0)
同样,程序因为出错了被迫停止,Python指出了出错的地方及错误内容。接下来,我们使用try-except代码块来避免这种情况发生:告诉Python,如果出现了这样的情况该怎么办。
3.2 使用try-except代码块处理异常
在编写代码时,我们可能会预知某些异常的发生,此时可以写一个try-except代码块来处理可能引发的异常。就像英文的意思一样,让Python试一下,如果行就继续;不行的话,那就执行别的代码(except),反正程序别停止了。我们对上述代码进行改写:
try:# 我们将导致错误的代码行print(5/0)放在了一个try代码块中。# 如果try代码块运行起来没问题,那就自动跳过except代码块# 如果try代码块中的代码导致了错误,Python将查找这样的except代码块,# 并运行其中的代码,即其中指定的错误与引发的错误相同print(5/0)
except ZeroDivisionError:print("You can't divide by zero! ")
3.3 使用异常避免崩溃
发生错误时,如果程序还有工作没有完成,妥善地处理错误就尤其重要。这种情况经常会出现在要求用户提供输人的程序中;如果程序能够妥善地处理无效输入,就能再提示用户提供有效输入,而不至于崩溃。下面来创建一个只执行除法运算的简单计算器:
print("Give me two numbers, and I'll divide them. ")
print("Enter 'q' to quit. ")while True:first_number = input("\nFirst number: ")if first_number == 'q':breaksecond_number = input("Second number: ")if second_number == 'q':breakanswer =int(first_number) / int(second_number)print(answer)
当以上这段代码输入除数为0的算式时,问题就出现了:
这种计算器除法程序可不是我们需要的,如果被怀有恶意的用户遇到,他们会得知很多信息,从而做一些破坏代码的事情。所以我们得使用try-except代码块,不过这里还得加一个“else”,相关的解释是:通过将可能引发错误的代码放在try-except代码块中,可提高这个程序抵御错误的能力。错误是执行除法运算的代码行导致的,因此我们需要将它放到try-except代码块中。这个示例还包含一个else代码块;依赖于try代码块成功执行的代码都应放到else代码块中。
print("Give me two numbers, and I'll divide them. ")
print("Enter 'q' to quit. ")while True:first_number = input("\nFirst number: ")if first_number == 'q':breaksecond_number = input("Second number: ")if second_number == 'q':break# try代码块里只能包含可能导致错误的代码。try :answer = int(first_number) / int(second_number)except ZeroDivisionError:# 出现错误后的解决代码print("You can't divide by 0. ")# 依赖于try代码块成功执行的代码都放在else代码块中else:print(answer)
try-except-else代码块工作原理小结:Python尝试执行try代码块中的代码;只有可能引发异常的代码才需要放在try语句中。有时候,有一些仅在try代码块成功执行时才需要运行的代码,这些代码应放在else代码块中。except代码块告诉Python,如果它尝试运行try代码块中的代码时引发了指定的异常,该怎么办。通过预测可能发生错误的代码,可编写健壮的程序,它们即便面临无效数据或缺少资源,也能继续运行,从而能够抵御无意的用户错误和恶意的攻击。
3.4 处理FileNotFundError异常
使用文件时,一种常见的问题是找不到文件:你要查找的文件可能在其他地方、文件名可能不正确或者这个文件根本不存在。对于所有这些情形,都可以使用try-except代码块以直观的方式进行处理。
filename = 'alice.txt'with open(filename) as f_obj:contents = f_obj.read()
这个错误是函数open()导致的,因此要处理这个错误,必须将try语句放在包含open()的代码之前:
filename = 'alice.txt'
try:with open(filename) as f_obj:contents = f_obj.read()
except FileNotFoundError:msg = "Sorry, the file " + filename + " does not exist. "print(msg)
四 处理文本
4.1 分析文本
我们将会分析整本书的文本文件,下面我们来提取童话“Alice in Wonderland”的文本,并尝试计算它包含多少个单词。我们将使用方法split(),它根据一个字符串创建一个单词列表。下面是对只包含童话名“”Alice in Wonderland“的字符串调用方法split(
)的结果:
title = 'Alice in Wonderland'
print(title.split())
方法split()以空格为分隔符将字符串拆成多个部分,并将这些部分都存储到一个列表中。结果是一个包含字符串中所有单词的列表,虽然有些单词可能包含标点。接下来我们使用这个方法,对整篇小说进行处理分析,统计这篇童话大致包含多少个单词:
filename = 'alice.txt'try:with open(filename) as f_obj:contents = f_obj.read()
except FileNotFoundError:msg = "Sorry, the file " + filename + " does not exist. "print(msg)
else:# 计算文件大致包含多少个单词words = contents.split()num_words = len(words)print("The file " + filename + " has about " + str(num_words) + " words")
4.2 使用多个文件
下面多分析几本书:
def count_words(filename):"""计算一个文件大致包含多少个单词"""try:with open(filename) as f_obj:contents = f_obj.read()except FileNotFoundError:msg = "Sorry, the file " + filename + " does not exist. "print(msg)else:# 计算文件大致包含多少个单词words = contents.split()num_words = len(words)print("The file " + filename + "has about " + str(num_words) + " words. ")# siddhartha.txt文件不存在,以此来展示程序的出色。
filenames = ['alice.txt','siddhartha.txt','moby_dict.txt','little_women.txt']
for filename in filenames:count_words(filename)
在这个示例中,使用try-except代码块提供了两个重要的优点:A、避免让用户看到traceback;B、让程序继续分析找到其他文件。
4.3 失败时一声不吭
上面这个例子,在我们找不到对应的文件夹时,Python会提示用户有一个文件没有找到。但并非每次捕获到异常时都需要告诉用户,有时候你希望程序发生异常时一声不吭:
我们可以将“except FileNotFoundError:”中的代码全部删除,改为pass。
def count_words(filename):"""计算一个文件大致包含多少个单词"""try:with open(filename) as f_obj:contents = f_obj.read()except FileNotFoundError:# msg = "Sorry, the file " + filename + " does not exist. "# print(msg)passelse:# 计算文件大致包含多少个单词words = contents.split()num_words = len(words)print("The file " + filename + "has about " + str(num_words) + " words. ")# siddhartha.txt文件不存在,以此来展示程序的出色。
filenames = ['alice.txt','siddhartha.txt','moby_dict.txt','little_women.txt']
for filename in filenames:count_words(filename)
又多了一个关键字“pass”,pass语句还充当了占位符,他提醒你在程序的某个地方什么都没做,并且以后也许要在这里做些什么。
4.4 决定报告哪些错误
在什么情况下该向用户报告错误?在什么情况下又应该在失败时一声不吭呢? 如果用户知道要分析哪些文件,他们可能希望在有文件没有分析时出现一条消息,将其中的原因告诉他们。如果用户只想看到结果,而并不知道要分析哪些文件,可能就无需在有些文件不存在时告知他们。
向用户显示他不想看到的信息可能会降低程序的可用性。Python的错误处理结构让你能够细致地控制与用户分亨错误信息的程度,要分享多少信息由你决定。编写得很好且经过详尽测试的代码不容易出现内部错误,如语法或逻辑错误,但只要程序依赖于外部因素,如用户输人、存在指定的文件、有网络链接,就有可能出现异常。凭借经验可判断该在程序的什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息。
五 存储数据
很多程序都要求用户输入某种信息,程序都把用户提供的信息存储在列表和字典等数据结构中。用户关闭程序时,你几乎总是要保存他们提供的信息。一种简单的方式是使用模块json来存储数据。
模块json让你能够将简单的Python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。你还可以使用json在Python程序之间分享数据。更重要的是,JSON数据格式并非Python专用的,这让你能够将以JSON格式存储的数据与使用其他编程语言的人分享。这是一种轻便格式,很有用,也易于学习。(JSON(JavaScript Object Notation)格式最初是为JavaScript开发的,但随后成了一种常见格式,被包括Python在内的众多语言采用)
5.1 使用json.dump()和json.load()
我们编写一个存储一组数字的简短程序,再编写一个将这些数字读取到内存中的程序。
import jsonnumbers = [2,3,5,7,11,13]filename = 'number.json'
# 以写的形式打开文件,使用的是json.dump()
with open(filename,'w') as f_obj:# 把数字写入文件json.dump(numbers,f_obj)# 读取数据,使用的是json.load()
with open(filename) as f_obj:# 为了区别numbers,后面加了下划线和2numbers_2 = json.load(f_obj)# 输出
print(numbers_2)
用记事本打开numbers.json文件,显示如下:
代码先导入了json模块,随后创建了一个数字列表,存储了一些数字。先是将数字存储到了json文件里,后又从json文件中读取出来。其中使用了json.dump()和json.load()方法。
5.2 保存和读取用户生成的数据
对于用户生成的数据,使用json保存将大有好处。因为如果不以某种方式进行存储,等程序停止运行时,用户的信息将会丢失。举个例子来说明:
将用户的姓名存储在json文件里:
# 导入json包
import json
# 需要用户输入一个名字
username = input("What is your name? ")
# 将json文件名存储在filename变量中
filename = 'username.json'
# 以写的方式打开文件,没有就创建一个json文件,并重新命名为f_obj
with open(filename,'w') as f_obj:# 使用json.dump()方法写入输入的名字json.dump(username,f_obj)# 输出提示,表明已将用户输入的名字存储了起来print("We'll remember you when you come back, " + username +"! ")
将json文件的内容再读取出来,比如对这个人进行问候:
# 导入json包
import json
# 将文件名存入filename变量中
filename = 'username.json'
# 读取json文件
with open(filename,'r') as f_obj:username = json.load(f_obj)# 打印问候语print("Welcome back, " + username + "! ")
下面我们将这两个程序合并:
# 导入json包
import json
# 如果以前存储了用户名就加载
# 否则,就提示用户输入用户名并存储
filename = 'username.json'
try:with open(filename) as f_obj:username = json.load(f_obj)
except FileNotFoundError:username = input("What is your name? ")with open(filename,'w') as f_obj:json.dump(username,f_obj)print("We will remember you when you back, " + username + "! ")
else:print("Welcome come back, " + username + "! ")
如果这个文件存在,就将其中的用户名读取到内存中,再执行else代码块,即打印一条欢迎用户回来的消息。用户首次运行这个程序时,文件username.,json不存在,将引发FileNotFoundError异常,因此Python将执行except代码块:提示用户输人其用户名,再使用json.dump()存储该用户名,并打印一句问候语。
5.3 重构
将代码划分为一系列完成具体工作的函数的过程被称为重构。重构让代码更清晰、更易于理解、更容易扩展。接下来,我们对上述问候用户的程序进行重构:
第一次重构:
# 导入json包
import jsondef greet_user():# 考虑到现在使用了一个函数,我们删除注释,取而代之的是一个文档字符串来指出程序是做什么的。"""问候用户,并指出其名字"""filename = "username.json"try:with open(filename) as f_obj:username = json.load(f_obj)except FileNotFoundError:username = input("What is your name? ")with open(filename, 'w') as f_obj:json.dump(username, f_obj)print("We will remember you when you back, " + username + "! ")else:print("Welcome come back, " + username + "! ")greet_user()
但上述的函数所做的不仅仅是问候用户,还在存储了用户时获取它,而在没有存储用户名时提示用户输入一个。所以我们可以对greet_user()再次重构:
# 导入json包
import jsondef get_stored_username():"""如果存储了用户名,就获取它"""filename = 'username.json'try:with open(filename) as f_obj:username = json.load(f_obj)except FileNotFoundError:return Noneelse:return usernamedef greet_user():"""问候用户,并指出其名字"""username = get_stored_username()if username:print("Welcome come back, " + username + "! ")else:username = input("What is your name? ")filename = "username.json"with open(filename,'w') as f_obj:json.dump(username,f_obj)print("We will remember you when you back, " + username + "! ")greet_user()
这是该文件不存在时的输入输出:
这是该文件存在时的输出:
上述代码的功能更细化了,我们还可以进一步重构:将greet_user()中的提示用户输入的代码放在一个独立的函数中。
# 导入json包
import jsondef get_stored_username():"""如果存储了用户名,就获取它"""filename = 'username.json'try:with open(filename) as f_obj:username = json.load(f_obj)except FileNotFoundError:return Noneelse:return usernamedef get_new_username():"""提示用户输入用户名"""username = input("What is your name? ")filename = "username.json"with open(filename, 'w') as f_obj:json.dump(username, f_obj)return usernamedef greet_user():"""问候用户,并指出其名字"""username = get_stored_username()if username:print("Welcome come back, " + username + "! ")else:username = get_new_username()print("We will remember you when you back, " + username + "! ")greet_user()
自行体会问候用户程序的三个版本的差异,可以看出代码的功能逐渐具体、清晰,易读性增强。因此,重构是编写代码中必不可少的一环。