掌握了编写组织有序而易于使用的程序所需的基本技能,该考虑让程序目标更明确,用途更大。本章中,将学习处理文件,让程序能够快速地分析大量的数据;我们将学习错误处理,避免程序在面对意外情形时崩溃;我们将学习异常,它们是Python创建的特殊对象,用于管理程序运行时出现的错误;我们还将学习模块json,它让我们能够保存用户数据,以免在程序停止运行后丢失。
学习处理文件和保存数据可以让我们的程序使用起来更容易:用户将能够选择输入什么样的数据,以及在什么时候输入;用户使用我们的程序做一些工作后,可将程序关闭,以后在接着往下做。学习处理异常可帮助我们应对文件不存在的情形,以及处理其他可能导致程序崩溃的问题。这让我们的程序在面对错误的数据时更健壮——不管这些错误数据源自无意的错误,还是源自破坏程度的恶意企图。我们在本章学习的技能可提高程序的适用性、可用性和稳定性。
10.1 从文件中读取数据
文本文件可存储的数据量巨大。每当需要分析或者修改存储在文件中的信息时,读取文件都很有用,对数据分析应用程序来说尤其如此。例如:你可以编写一个程序:读取文本文件的内容,重新设置这些数据格式并将其写入文件,让浏览器能显示这些内容。
要使用文本文件中信息,首先需要将信息读取到内存中。为此,你可以一次性读取文件全部内容,也可以每次一行的方式逐步读取。
10.1.1 读取整个文件
要读取文件,需要一个包含几行文本的文件。下面首先来创建一个文件,它包含精确到小数点后30位的圆周率值,且在小数点后每10位处都换行:
pi_digits.text
3.141592653589793238462643383279
要手动尝试后续示例,可在编辑器中输入这些数据行,再将文件保存为pi_digits.text.
file_reader.py
with open('pi_digits.text') as file_object: contents = file_object.read() 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将这个对象存储在我们将在后面使用的变量中。
关键字with在不需要访问文件后将其关闭。这个程序中,我们注意到我们调用open(),但没有调用close;你可以可以调用open()和close()来打开和关闭文件,但这样做,如果程序存在bug,导致close语句未执行,文件将不会关闭。看似微不足道,但未妥善地关闭文件可能会导致数据丢失或受损。如果在程序中过早地调用close(),会发现需要使用文件时它已关闭(无法访问),这会导致更多错误。并非在任何情况下都能轻松确定关闭文件的恰当时机,但通过使用前面所示结构,可让python去确定:只管打开文件,并在需要时使用,python会在合适时候自动关闭。
有了表示pi_digits.txt的文件对象后,我们使用方法read()(程序前2行)读取这个文件的全部内容,并将其作为一个长的字符串存储在变量contents中。这样,通过打印contents的值,就可将这个文本文件的全部内容显示出来。
相比于原始文件,改输出唯一不同的地方是末尾多了一个空行。因为read()到达文件末尾时返回一个空字符串,而这个空字符串显示出来时就是一个空行。要删除多出来的空行,可在print()语句中使用rstrip():
with open('pi_digits.text') as file_object: contents = file_object.read() print(content.srstrip())
python方法rstrip()删除字符串末尾空白。
10.1.2 文件路径
当你将类似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中,你可以这样编写代码:
with open('text_files/filename.txt') as file object:
这行代码让Python到文件夹python_work下的文件夹text_files中去查找指定的.txt文件。在Windows系统中,在文件路径使用反斜杠(\ ):
with open('text_files\filename.txt') as 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'with open(file_path) as 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系统,且结果不符合预期,确保在文件路径中使用的是反斜杠。
10.1.3 逐行读取
读取文件时,常常要检查其中的每一行:你可能要在文件中查找特定的信息,或者要以某种方式修改文件中的文本。例如,你可能要遍历一个包含天气数据的文件,并使用天气描述中包含字样sunny的行。在新闻报道中,你可额能会检查包含标签<headline>的行,并按特定格式设置它。
要以每次一行的方式检查文件,可对文件对象使用for循环:
file_reader.py
filename = 'pi_digits.text'with open(filename) as file_object: for line in file_object: print(line)
在filename =‘pi_digits.txt’处,我们将要读取的文件名称存储在变量filename中,这是使用文件时一种常见做法。由于变量filename表示的并非实际文件——它只是一个让python知道到哪里去查找文件的字符串,因此可轻松地将‘pi_digits.txt’替换为需要使用的另一个文件名称。调用open()后,将一个表示文件及其内容的对象存储到变量file_object中(with open(filename)as file_object:)这里也使用关键字with,让python负责妥善地打开和关闭文件。为查看文件内容,我们通过对文件对象执行循环来遍历文件中的每一行
我们打印每一行时,发现空白行更多了,因为每行的末尾都有一个看不见的换行符,而print语句也会加上一个换行符,因此每行末尾都有两个换行符:一个来自文件,另一个来自print语句。要消除这些多余的空白行,可在print语句中使用rstrip():
filename = 'pi_digits.txt'with open(filename) as file_object: for line in file_object: print(line.rstrip())
10.1.4 创建一个包含文件各行内容的列表
使用关键字with时,open()返回的文件对象只在with代码块内可用。如果要在with代码块外访问文件的内容,可在with代码块内将文件的各行存储在一个列表中,并在with代码块外使用该列表:你可以立即处理文件的各个部分,也可推迟到程序后面再处理。
下面的实例在with代码块中将文件pi_digits.txt的各行存储在一个列表中,再在with代码块外打印他们:
filename = 'pi_digits.txt'with open(filename) as file_object: lines = file_object.readlines() for line in lines: print(line.rstrip())
在lines = file_object.readlines()处的方法readlines()从文件中读取每一行,并将其存储在一个列表中;接下来,该列表被存储到变量lines中;在with代码块中,我们依然可以使用这个变量。接下来,使用一个简单的for循环打印lines中的各行。
10.1.5 使用文件内容
将文件读取到内存中后,就可以以任何方式使用这些数据。例:
首先创建一个字符串,它包含文件中存储的所有数字,且没有任何空格:
pi_string.py
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)print(len(pi_string)) 输出:
3.1415926535 8979323846 2643383279
36先打开文件,将其中所有行都存储在一个列表中。在pi_string = ' '处,我们创建一个变量pi_string,用于存储圆周率的值。接下来,使用一个循环将各行都加入到pi_string,并删除每行末尾的换行符(rstrip())。
在变量pi_string存储的字符串中,包含原来位于每行左边的空格,为删除这些空格,可使用strip()而不是rstrip():输出结果:
输出:3.14159265358979323846264338327932
注意 读取文本文件时,python将其中的所有文本都解读为字符串。如果你读取的是数字,并要建起作为数值使用,就必须使用函数int()将其转换为整数,或使用函数float()将其转换为浮点数。
10.1.6 包含一百万位的大型文件
如果有一个文本文件,需要到1000 000位圆周率的值,也可创建一个包含所有这些数字的字符串。为此,无需对前面程序做任何修改,只需将这个文件传递给它。对于可处理的数据量,python没有任何限制。
10.1.7 圆周率值中包含你的生日吗
扩展所编写程序,可将生日表示为一个由数字组成的字符串,在检查这个字符串是否包含在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:")if birthday in pi_string: print("your birthday appear in the first digits of pi!")else: print("Your birthday does not appear in the first million digits of pi")
练习:
10-1 Python学习笔记:在文本编辑器中新建一个文件,写几句话来总结一下你至此学到的Python只是,其中每一行都以“In Python you can”打头。将这个文件命名为learning_python.txt,并将其存储到为完成本章练习而编写的程序所在的目录中。编写一个程序,它读取这个文件,并将我们所写的内容打印三次:第一次打印是读取整个文件;第二次打印时遍历文件对象;第三次及打印是将各行存储在一个列表中,再在with代码块外打印它们。
filename = 'lenrning_python'
with open(filename) as file_object: contents = file_object.read() print(contents) with open(filename) as file_object: for line in file_object: print(line.strip())print("\n")
with open(filename) as file_object: lines = file_object.readlines() for line in lines: print(line.strip())运行结果如下:
In Python you can lenrn how to develop game.
In python you can learn how to do peration and maintenance. In python you can lenrn how to do data analysis. In python you can learn a lot of things which you can think. In Python you can lenrn how to develop game. In python you can learn how to do peration and maintenance. In python you can lenrn how to do data analysis. In python you can learn a lot of things which you can think. In Python you can lenrn how to develop game. In python you can learn how to do peration and maintenance. In python you can lenrn how to do data analysis. In python you can learn a lot of things which you can think.10-2 C语言学习笔记:可使用方法replace()将字符串中的特定单词都替换为另一个单词。下面是一个简单的示例,演示了如何将句子中的'dog'替换为'cat':
>>> message = "I really like dogs"
>>> message.replace('dog','cat')'I really like cats'读取我们刚创建的文件lenrning_python.txt中的每一行,将其中的Python都替换为另一门语言的名称,如C。将修改后的各行都打印到屏幕上。
filename = 'lenrning_python' lines = [] with open(filename) as file_object: for line in file_object: string = line.replace('python',"C") lines.append(string) for message in lines: print(message.strip()) 运行如果如下: In C you can lenrn how to develop game. In C you can learn how to do peration and maintenance. In C you can lenrn how to do data analysis. In C you can learn a lot of things which you can think.
10.2.1写入空文件
要将文本写入文件,在调用open()时需要提供另一个实参,告诉python要写入打开的文件。
write_message.py
filename = 'programming.txt'with open(filename, 'w') as file_object: file_object.write("I love programming.")
这个示例中,调用open()时提供两个实参(filename,‘w’)。第一个实参也是要打开的文件名称;第二个实参(‘w’)告诉python,要以写入模式打开这个文件。打开文件时,可指定读取模式‘’('r')、写入模式(‘w’)、附加模式(‘a’)或者让你能够读取和写入文件的模式(‘r+’)。如果省略模式实参,python将以默认的制度模式打开文件。
注意:如果要写入的文件不存在,函数open()将自动创建它。然而,以写入(‘w’)模式打开文件时要小心,因为如果指定的文件已经存在,python将在返回文件对象前清空该文件。
在file_object.write(" ")处,我们使用文件对象的方法write()将一个字符串写入文件。这个程序没有终端输出,但如果你打开文件programming.txt,将看到如下一行内容:
I love programming.
相比于计算机中的其他文件,这个文件没有什么不同。可编写,操作等
注意:python只能讲字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数str()将其转化为字符串格式。
10.2.2 写入多行
函数wirte()不会再你写入的文本末尾添加换行符,因此如果你写入多行时没有指定换行符,文件看起来可能不是你希望的那:
filename = 'programming.txt'with open(filename, 'w') as file_object: file_object.write("I love programming.") file_object.write("I love creating new games.")
如果你打开programming.txt,将发现两行内容挤在一起:
I love programming.I love creating new games.
要让每个字符串度单独占一行,需要在write()语句中国包含换行符:
filename = 'programming.txt'with open(filename, 'w') as file_object: file_object.write("I love programming.\n") file_object.write("I love creating new games.\n")
10.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")
在with open() as file_object:处,我们打开文件时指定实参‘a’,以便将内容附加到文件末尾,而不是覆盖文件原来内容。在后面继续写入2行,他们被添加到文件programming.txt末尾:
programming.txt
I love programming.I love creating new games.I also love finding meaning in large datasets.I love creating apps that can run in a browser.
最终的结果是,文件原来内容还在,后面是我们新添加的内容。
练习:编写一个 while 循环,提示用户输入其名字。用户输入其名字后,在屏幕上打印一句问候语,并将一条访问记录添加到文件 guest_book.txt 中。确保这个文件中的每条记录都独占一行:
filename = 'guest.txt'with open(filename, 'a') as file_object: #此要求为添加每一条记录,'w'写入会覆盖 while True: print("Enter 'q' to quit the programmer:") guest_name = input("Please enter your name:") if guest_name=='q': break else: print("Hello "+ guest_name.title()+ ",welcome join us!") file_object.write(guest_name.title()+"visit this item.\n")
练习:
10-3 访客:编写一个程序,提示用户输入其名字;用户做出响应后,将其名字写入到文件guest.txt中。 分析:由于是写入,因为我们不可能覆盖前面一个来访的人,这也是不被允许的,因为采用的追加模式。 filename = 'guest.txt' with open(filename,'a') as file_object: guest = input("Please input your name: ") file_object.write(guest.title() + "\n") 10-4 访客名单:编写一个while循环,提示用户输入其名字,用户输入名字后,在屏幕上打印一条问候语,并将一条访问记录添加到文件 guest_book.txt中。确保这个文件中的每天记录都独占一行。 filename = 'guest_book.txt' with open(filename,'a') as file_object: while True: print("Enter 'q' to quit the programmer: ") guest = input("Please input your name: ") if guest == 'q': break else: print("Hello, " + guest) file_object.write(guest.title() + "\n") 运行结果如下: Enter 'q' to quit the programmer: Please input your name: geng changxue Hello, geng changxue Enter 'q' to quit the programmer: Please input your name: q
10.3 异常
每当python发生不知所措的错误时,它会创建一个异常对象。如果你编写处理该异常的代码,程序将继续运行;如果未处理,程序将停止,并显示一个traceback,其中包含有关异常报告。
异常是使用try-except代码块处理的。try-except代码块让python执行指定的操,同时告诉python发生异常时怎么办。使用try-except代码块时,即便出现异常,程序也将继续运行:显示你编写好的友好的错误消息,而不是令用户迷惑的traceback。
10.3.1 处理zeroDivisionError异常
division.py(一个简单的异常)
pint(5/0)
一个traceback显示:
Traceback (most recent call last): File "division.py", line 1, inprint(5/0)ZeroDivisionError: division by zero
最后一行ZeroDivisionError是一个异常对象。Python无法按照你要求做的时候就会创建这种对象。下面我们将告诉python发生这种错误怎么办
10.3.2 使用try-except代码块
当你你认为可能发生错误时,可编写一个try-except代码块来处理可能发生的异常。让python尝试运行一些代码,并告诉它如果这些代码发生了指定的异常,应怎么办。
处理ZeroDivisionError异常的try-except代码块类似于下面这样:
try:print(5/0)except ZeroDivisionError: print("You can't divid by zero!")
我们将导致错误的代码行print(5/0)放在一个try代码块中。如果try代码块中的代码运行起来没有什么问题,python将跳过except代码块;如果try代码块中的代码导致错误,python将查找这样的except代码块,并 运行期中代码,即其中只等的错误与引发的错误相同。
这个示例中,try代码块中的代码引发ZeroDivisionError异常,因此python指出该如何解决问题的except代码块,并运行其中代码。这样,用户号看到的是一条友好错误消息,而不是traceback:
You can't divide by zero!
如果try-except代码块后面还有其他代码,程序将继续运行,因为已经告诉python这种错误如何处理。
10.3.3 使用异常避免崩溃
如果程序能够妥善处理无效输入,就能提示用户有效输入,而不至于崩溃。例(创建一个知心除法运算的简单计算器):
division.py
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': break second_number = input("Second number: ") if second_number == 'q': break answer = int(first_number)/int(second_number) print(answer)
这个程序提示用户输入一个数字,并将其存储到first_number中;如果用户输入的不是表示退出的q,就提示用户再输入一个数字,并将其存储到变量second_number中。接下,计算这个两个数字的商(answer)。这个程序没有采取任何处理错误的措施,因此让它执行除数为0的除法运算时,它将奔溃:
Give me two numbers,and i'll divide them.Enter 'q' to quit.First number: 5Second number: 0Traceback (most recent call last): File "division.py", line 11, inanswer = int(first_number)/int(second_number)ZeroDivisionError: division by zero
程序奔溃,用户将看到traceback。有时候,训练有素的攻击者将根据这些信息判断对你的代码发起什么样攻击。
10.3.4 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': break second_number = input("Second number: ") if second_number == 'q': break try: answer = int(first_number)/int(second_number) except ZeroDivisionError: print("You can't divide by 0!") else: print(answer)
我们让python尝试执行try代码块中的除法运算,这个代码块只包含可能导致错误的代码。依赖于try代码块的执行都放在else代码块中;在这个示例中,如果除法成功,我们使用else代码块来打印结果(如else代码块)。
except代码块告诉python,出现ZeroDivisionError异常时怎么办。如果try代码块因除零错误而失败,我们就打印一条友好的消息,告诉用户如何避免这种错误。程序将继续运行,用户看到不到traceback:
Give me two numbers,and i'll divide them.Enter 'q' to quit.First number: 4Second number: 0You can't divide by 0!First number: 8Second number: 90.8888888888888888
try-except-else代码块工作原理大致如下:python尝试执行try代码块中的代码;只有可能引发异常的代码才需要放在try语句中。有时候,有一些仅在try代码块成功执行时才需要的运行的代码;这些代码应放在else代码块中。except代码块告诉python,如果它尝试运行try代码块中的代码引发指定的异常该如何处理。
通过预测可能发生的错误代码,可编写健壮的程序,他们即便面临无效数据或缺少资源,也能继续运行,从而抵御无意的用户错误和恶意攻击。
10.3.5 处理FileNotFoundError异常
使用文件时,一种常见的问题是找不到文件:你要查找的文件可能在其他地方、文件名可能不正确或者这个文件根本就不存在。对于所有这些情形,都可使用 try-except 代码
块以直观的方式进行处理。我们来尝试读取一个不存在的文件。下面的程序尝试读取文件 alice.txt 的内容,但我没有将这个文件存储在 alice.py 所在的目录中。
alice.py
filename = 'alice.txt'with open(filename) as f_obj: contents = f_ojc.read()
python无法读取不存的文件,因此它引发一个异常:
Traceback (most recent call last): File "alice.py", line 3, inwith open(filename) as f_obj:FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'
在上述的traceback中,最后一行报告了FileNotFoundError异常,这是Python找不到要打开的文件时创建的异常。这个示例中,这个错误就是函数open()导致的,因此要处理这个错误,必须将try语句放在open()代码之前:
filename = 'alice.txt'try: with open(filename) as f_obj: contents = f_ojc.read()except FileNotFoundError: msg = "sorry, the file "+ filename +"does not exist." print(msg)
在这个示例中,try代码块引发FileNotFoundError异常,因此python找出与该错误匹配的except代码块,并运行其中的代码。最终的结果四显示一条友好的错误消息,而不是traceback:
Sorry,the file alice.txt does not exist.
10.3.6 分析文本
下面来提取通话Alice in wonderland的文本,并尝试计算它包含多少个单词。我们将使用方法split(),它根据一个字符串创建一个单词列表。下面是对只包含童话名“Alice in Wonderland”的字符串调用方法split()的结果:
title = "Alice in Wonderland"
title.split()
['Alice', 'in', 'Wonderland']
方法split()以空格为分隔符将字符串拆分成多个部分,
并将这些部分都存储到一个列表中。结果是一个包含字符串中所有单词的列表,虽然有些单词可能包含标点。为计算Alice in Wonderland 包含多少个单词,我们将对整篇小说调用 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.")输出:The file alice.txthas about 29397words.或:sorry, the file alice.txt does not exist.
把alice.txt移到正确的目录中,让try代码块能够成功滴执行。在words = contents处,对变量contents(现在它是长字符串,包含通话Alice in Wordland的全部文本)调用方法split(),以生成一个列表,其中包含这部童话中的所有单词。当我们使用len()来确定这个列表的长度时,就知道原始字符串打字包含多少个单词。这些代码都放在else代码块中,仅当try代码块成功执行时候带执行他们。
10.3.7 使用多个文件
先将程序大部分代码移到一个名为count_words()的函数中,这样对多本书分析时候将更容易:
Word_count.py
def count_words(fliename): 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.")filename = 'alice.txt'count_words(filename)
这些代码大都与原来一样,我们只是将它们移到了函数 count_words() 中,并增加了缩进量。修改程序的同时更新注释是个不错的习惯,因此我们将注释改成了文档字符串。
现在可以编写一个简单的循环,计算要分析的任何文本包含多少个单词了。为此,我们将要分析的文件的名称存储在一个列表中,然后对列表中的每个文件都调用coun_words()。
def count_words(fliename): --snip--filenames = ['alice.txt','siddhatha.txt', 'moby.txt', 'little_woman.txt']for filename in filenames: count_words(filename) 输出:
The file alice.txt has about 29397 words.
Sorry, the file siddhatha.txt does not exist.Sorry, the file moby.txt does not exist.Sorry, the file little_woman.txt does not exist.在这个示例中,使用 try-except 代码块提供了两个重要的优点:避免让用户看到 traceback ;让程序能够继续分析能够找到的其他文件。如果不捕获因找不到 siddhartha.txt 而引发的FileNotFoundError 异常,用户将看到完整的 traceback ,而程序将在尝试分析 Siddhartha 后停止运行 —— 根本不分析 Moby Dick 和 Little Women 。
10.3.8 失败时一声不吭
要让程序在失败时一声不吭,可像通常那样编写 try 代码块,但在 except 代码块中明确地告诉 Python 什么都不要做。 Python 有一个 pass 语句,可在代码块中使用它来让 Python什么都不要做:
def count_words(fliename): --snip-- except FileNotFoundError: pass #在Python代码块中跳过 else: --snip--filenames = ['alice.txt','siddhatha.txt', 'moby.txt', 'little_woman.txt']for filename in filenames: count_words(filename) 输出: The file alice.txt has about 29397 words.
出现 FileNotFoundError 异常时,将执行 except 代码块中的代码,但什么都不会发生。这种错误发生时,不会出现 traceback ,也没有任何输出。用户将看到存在的每个文件包含多少个单词,但没有任何迹象表明有一个文件未找到。
pass 语句还充当了占位符,它提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么。10.4 存储数据
很多程序都要求用户输入某种信息,如让用户存储游戏首选项或提供要可视化的数据。不管专注的是什么,程序都把用户提供的信息存储在列表和字典等数据结构中。用户关闭程序时,你几乎总是要保存他们提供的信息;一种简单的方式是使用模块 json 来存储数据。
模块 json 让你能够将简单的 Python 数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。你还可以使用 json 在 Python 程序之间分享数据。更重要的是, JSON 数据格式并非 Python 专用的,这让你能够将以 JSON 格式存储的数据与使用其他编程语言的人分享。
注意:JSON(JavaScript Object Notation)格式最初是JavaScript开发的,但随后成了一种常见格式,被包括python在内的众多语言采用。10.4.1 使用json.dump()和json.load()
接下来编写一个存储一组数字的简短程序,再编写一个将这些数字读取到内存中的程序。第一个程序将使用json.dump()来存储这组数字,而第二个程序将使用json.load()。
函数json.dump()接收两个实参:要存储数据以及可用于存储数据的文对象。下面演示如何使用json.dump()来存储数字列表。
number_writer.py
import jsonnumbers = [2, 3, 4, 5, 6, 7, 11, 13]filename = 'numbers.json'with open(filename, 'w') as f_obj: json.dump(numbers,f_obj)
我们先导入模块 json ,再创建一个数字列表。在filename处,我们指定了要将该数字列表存储到其中的文件的名称。通常使用文件扩展名 .json 来指出文件存储的数据为 JSON 格式。接下来,我们以写入模式打开这个文件,让 json 能够将数据写入其中。后面,我们使用函数 json.dump() 将数字列表存储到文件 numbers.json 中。
这个程序没有输出,但我们可以打开文件 numbers.json ,看看其内容:
[2, 3, 4, 5, 6, 7, 11, 13]
下面再编写一个程序,使用 json.load() 将这个列表读取到内存中:
number_reader.py
import jsontry: filename = 'numbers.json' with open(filename) as f_obj: numbers = json.load(f_obj)except FileNotFoundError: msg = "sorry, the file"+ filename+" is not exist." print(msg)else: print(numbers)输出:[2, 3, 4, 5, 6, 7, 11, 13]
这次我们以读取方式打开这个文件,因为 Python 只需读取这个文件。使用函数 json.load() 加载存储在numbers.json 中的信息,并将其存储到变量 numbers 中。
10.4.2 保存和读取用户生成的数据
对于用户生成的数据,使用 json 保存它们大有裨益,因为如果不以某种方式进行存储,等程序停止运行时用户的信息将丢失。下面来看一个这样的例子:用户首次运行程序时被提示输入自己的名字,这样再次运行程序时就记住他了。先来存储用户的名字:
remember_me.py
import jsonusername = input("what's your name?\n")filename = 'username.json'with open(filename,'w') as f_obj: json.dump(username,f_obj) print("we will remember you when you come back, "+username +"!")输出:what's your name?Mackwe will remember you when you come back, Mack!
我们提示输入用户名,并将其存储在一个变量中。接下来,我们调用 json.dump() ,并将用户名和一个文件对象传递给它,从而将用户名存储到文件中。
现在再编写一个程序,向其名字被存储的用户发出问候:
greet_user.py
import jsonfilename = 'username.json'with open(filename) as f_obj: #读与写入的差异,with open(filename,'w') as f_obj: username = json.load(f_obj) #存储和读处差异 json.dump(username, f_obj) print("wlcome back, "+ username +"!")输出:wlcome back, Mack!
我们使用 json.load() 将存储在 username.json 中的信息读取到变量 username 中。恢复用户名后,我们就可以欢迎用户回来了。
我们需要将这两个程序合并到一个程序( remember_me.py )中。这个程序运行时,我们将尝试从文件 username.json 中获取用户名,因此我们首先编写一个尝试恢复用户名的 try 代码块。如果这个文件不存在,我们就在 except 代码块中提示用户输入用户名,并将其存储在 username.json 中,以便程序再次运行时能够获取它:
remember_me.py
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?\n") with open(filename, 'w') as f_obj: json.dump(username, f_obj) print("we'll remember you when you come back, "+ username +"!")else: print("welcome back "+ username +"!")
在第一个open(filename) as f_obj处,尝试打开文件 username.json 。如果这个文件存在,就将其中的用户名读取到内存中username = json.load(f_obj),再执行 else 代码块,即打印一条欢迎用户回来的消息。用户首次运行这个程序时,文件 username.json 不存在,将引发 FileNotFoundError 异常,因此 Python 将执行 except 代码块:提示用户输入其用户名,再使用 json.dump() 存储该用户名,并打印一句问候语。
无论执行的是 except 代码块还是 else 代码块,都将显示用户名和合适的问候语。如果这个程序是首次运行,输出将如下:what is your name?Mackwe'll remember you when you come back, Mack!
否则,输出:
welcome back Mack!
10.4.3 重构
代码能够正确地运行,但可做进一步的改进 —— 将代码划分为一系列完成具体工作的函数。这样的过程被称为 重构 。重构让代码更清晰、更易于理解、更容易扩展。
要重构 remember_me.py ,可将其大部分逻辑放到一个或多个函数中。 remember_me.py 的重点是问候用户,因此我们将其所有代码都放到一个名为 greet_user() 的函数中:
remember_me.py
import jsondef greet_user(): """问候用户,并指出其名字""" filename = 'username.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: username = input("what's your name?\n") with open(filename, 'w') as f_obj: json.dump(username, f_obj) print("we'll remember you when you come back, "+ username +"!") else: print("welcome back "+ username +"!")greet_user()
函数 greet_user() 所做的不仅仅是问候用户,还在存储了用户名时获取它,而在没有存储用户名时提示用户输入一个。
下面来重构 greet_user() ,让它不执行这么多任务。为此,我们首先将获取存储的用户名的代码移到另一个函数中:
import jsondef get_sorted_username(): """如果存储了用户名,就获取它""" filename = 'username.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: return None else: return usernamedef greet_user(): """问候用户,并指出其名字""" username = get_sorted_username() if username: print("welcome back,"+username +"!") else: username = input("what's your name?\n") filename = 'username.json' with open(filename, 'w') as f_obj: json.dump(username,f_obj) print("we will remember you when you come back,"+ username +"!")greet_user()
新增的函数 get_stored_username() 目标明确,开始处的文档字符串指出了这一点。如果存储了用户名,这个函数就获取并返回它;如果文件 username.json 不存在,这个函数就返回 None 。这是一种不错的做法:函数要么返回预期的值,要么返回 None ;这让我们能够使用函数的返回值做简单测试。在if username:处,如果成功地获取了用户名,就打印一条欢迎用户回来的消息,否则就提示用户输入用户名。
需将 greet_user() 中的另一个代码块提取出来:将没有存储用户名时提示用户输入的代码放在一个独立的函数中:
import jsondef get_sorted_username(): """如果存储用户名,就获取它""" --snip--def get_new_username(): """提示用户输入用户名""" username = input("What's your name?\n") filename = 'username.json' with open(filename,'w') as f_obj: json.dump(username, f_obj) return username def greet_user(): """问候用户,并指出其名字""" username = get_sorted_username() if username: print("welcome back,"+username +"!") else: username = input("what's your name?\n") filename = 'username.json' with open(filename, 'w') as f_obj: json.dump(username,f_obj) print("we will remember you when you come back,"+ username +"!")greet_user()
在 remember_me.py 的这个最终版本中,每个函数都执行单一而清晰的任务。调用 greet_user() ,它打印一条合适的消息:要么欢迎老用户回来,要么问候新用户。为此,它首先调用 get_stored_username() ,这个函数只负责获取存储的用户名(如果存储了的话),再在必要时调用 get_new_username() ,这个函数只负责获取并存储新用户的用户名。要编写出清晰而易于维护和扩展的代码,这种划分工作必不可少
10.5 小结
本章中,你学习了:如何使用文件;如何一次性读取整个文件,以及如何以每次一行的方式读取文件的内容;如何写入文件,以及如何将文本附加到文件末尾;什么是异常以及如何处理程序可能引发的异常;如何存储 Python 数据结构,以保存用户提供的信息,避免用户每次运行程序时都需要重新提供。
练习:
喜欢的数字:编写一个程序,提示用户输入他喜欢的数字,并使用json.dump()将这个数字存储到文件中。在编写一个程序,从文件中读取这个值并打印消息"I know your favorite number!It's ."
import jsonfilename = 'favorite.json'with open(filename, 'w') as f_obj:"""先打开文件,再写入用户输入""" favorite_num = input("What's your favorite num: ") json.dump(favorite_num, f_obj) #dump()第一个元素指代 input内容为favorite_num print("we'll remember your favorite number "+str(favorite_num)+".") import jsonfilename = 'favorite.json'with open(filename) as f_obj: json.load(filename,'r') #更正 M = json.load(f_obj) print("I know your favorite number! It's "+ str(filename)) #更正 str(M)
记住喜欢的数字:将上个练习中的两个程序合二为一。如果存储了用户喜欢的数字,就向用户显示它,否则提示用户输入他喜欢的数字并将其存储到文件中。运行这个程序两次,看是否向预期那样工作。
import json"""导入模块json"""def get_stored_favorite_num(): """查看是否存在用户喜欢的数字""" try: filename = 'lucky_number.json' with open(filename) as f_obj: favorite_number = json.load(f_obj) except FileNotFoundError: return None else: return favorite_numberdef get_favorite_num(): #如果文件不存在,则让用户重新输入并存储它 favorite_number = input("please enter your favorite number: ") filename = 'lucky_number.json' with open(filename,'w') as f_obj: json.dump(favorite_number,f_obj) return favorite_numberdef favorite_num(): #返回用户新欢的数字 favorite_number = get_stored_favorite_num() if favorite_number: print("I know your favorite number is "+ str(favorite_number)+".") else: favorite_number = get_favorite_num() print("I'll remember your favorite number: "+str(favorite_number))favorite_num()
10-13 验证用户:最后一个remember_me.py版本假设用户要么已输入其用户名,要么是首次运行该程序。我们应修改这个程序,以应对这样的情形: 当前和最后一次运行该程序的用户并非同一个人。 为此,在greet_user()中打印欢迎回来的消息前,先询问他用户名是否是对的。如果不对,就调用get_new_username()让用户输入正确的用户名。 import json def get_stored_username(): '''如果存储了用户名,就获取它''' try: filename = 'username.json' with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: return None else: while True: #判断用户名是否相同,这里要求用户输入相同的名字我们才欢迎他,不然会一直让用户输入 yourname = input("Enter your username: ") if username == yourname: return username break else: print("\nThe user name you entered is incorrect, please re-enter.") def 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 username def greet_user(): '''问候用户,并指出其名字''' username = get_stored_username() if username: print("Welcome back, " + username + "!") else: username = get_new_username() print("We'll remember you when you come back, " + username + "!") greet_user()