目录
3. 异常
【处理ZeroDivisionError异常】
【使用异常避免崩溃】
【else代码块】
【处理FileNotFoundError异常】
【分析文本】
【使用多个文件】
【失败时一声不吭】
【决定报告哪些错误】
【动手试一试】
4.存储数据【json】不管专注的是什么,程序都把用户提供的信息存储在列表和字典等数据结构中。用户关闭程序时,你几乎总是要保存他们提供的信息;一种简单的方式是使用模块json来存储数据。
【使用json.dump()储存新文件和json.load()第二个文件调用第一个】
【重构】
【练习】
5.小结
Python使用被称为异常的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果你编写了处理该异常的代码,程序将继续运行;如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告。
异常是使用try-except代码块处理的。try-except代码块让Python执行指定的操作,同时告诉Python发生异常时怎么办。使用了try-except代码块时,即便出现异常,程序也将继续运行:显示你编写的友好的错误消息,而不是令用户迷惑的traceback。
Traceback (most recent call last):
File "division.py",line 1,in
print(5/0)
ZeroDivisionError:division by zero ❶
【使用try-except代码块】
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
当你认为可能发生了错误时,可编写一个try-except代码块来处理可能引发的异常。
我们将导致错误的代码行print(5/0)放在了一个try代码块中。如果try代码块中的代码运行起来没有问题,Python将跳过except代码块;如果try代码块中的代码导致了错误,Python将查找这样的except代码块,并运行其中的代码,即其中指定的错误与引发的错误相同
如果try-except代码块后面还有其他代码,程序将接着运行,因为已经告诉了Python如何处理这种错误。下面来看一个捕获错误后程序将继续运行的示例。
除法程序
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)
这个程序没有采取任何处理错误的措施,因此让它执行除数为0的除法运算时,它将崩溃。
如果用户怀有恶意,他会通过traceback获悉你不希望他知道的信息。例如,他将知道你的程序文件的名称,还将看到部分不能正确运行的代码。有时候,训练有素的攻击者可根据这些信息判断出可对你的代码发起什么样的攻击。
先在检测的时候,自己发现问题。然后再放入try-except中.
---omit---
try:
answer = int(first_number) / int(second_number)
except ZeroDivisionError:
print(f"You can't divide by zero.")
else:
print(answer)
except代码块告诉Python,出现ZeroDivisionError异常时该怎么办。如果try代码块因除零错误而失败,我们就打印一条友好的消息,告诉用户如何避免这种错误。程序将继续运行,用户根本看不到traceback。
try-except-else代码块的工作原理大致如下:Python尝试执行try代码块中的代码;只有可能引发异常的代码才需要放在try语句中。
else: 有时候,有一些仅在try代码块成功执行时才需要运行的代码;这些代码应放在else代码块中。except代码块告诉Python,如果它尝试运行try代码块中的代码时引发了指定的异常,该怎么办。
你要查找的文件可能在其他地方、文件名可能不正确或者这个文件根本就不存在。
Traceback (most recent call last):
File "alice.py",line 3,in
with open(filename) as f_obj:
FileNotFoundError:[Errno 2] No such file or directory:'alice.txt'
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)
本节使用的文本来自项目Gutenberg(http://gutenberg.org/),这个项目提供了一系列不受版权限制的文学作品,可以打印纯txt形式。如果你要在编程项目中使用文学文本,这是一个很不错的资源。
提取:方法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.")
我们对变量contents(它现在是一个长长的字符串,包含童话Alice in Wonderland的全部文本)调用方法split(),以生成一个列表,其中包含这部童话中的所有单词。
当我们使用len()来确定这个列表的长度时,就知道了原始字符串大致包含多少个单词。
我们打印一条消息,指出文件包含多少个单词。这些代码都放在else代码块中,因为仅当try代码块成功执行时才执行它们。输出指出了文件alice.txt包含多少个单词。
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.")
filename = 'alice.txt'
count_words(filename)
把功能移动到函数中
现在可以编写一个简单的循环,计算要分析的任何文本包含多少个单词了。为此,我们将要分析的文件的名称存储在一个列表中,然后对列表中的每个文件都调用count_words()。
下载txt形式文档,把书名存储在列表里。
def count_words(filename):
--snip--
filenames = ['alice.txt','siddhartha.txt','moby_dick.txt','little_women.txt']
for filename in filenames:
count_words(filename)
文件siddhartha.txt不存在,但这丝毫不影响这个程序处理其他文件
结果:
The file alice.txt has about 29461 words.
Sorry,the file siddhartha.txt does not exist.
The file moby_dick.txt has about 215136 words.
The file little_women.txt has about 189079 words.
在这个示例中,使用try-except代码块提供了两个重要的优点:避免让用户看到traceback;让程序能够继续分析能够找到的其他文件。
def count_words(filename):
"""计算一个文件大致包含多少个单词"""
try:
--snip--
except FileNotFoundError:
pass #1
else:
--snip--
filenames = ['alice.txt','siddhartha.txt','moby_dick.txt','little_women.txt']
for filename in filenames:
count_words(filename)
这个程序唯一不同的地方是❶处的pass语句。现在,出现FileNotFoundError异常时,将执行except代码块中的代码,但什么都不会发生。这种错误发生时,不会出现traceback,也没有任何输出。用户将看到存在的每个文件包含多少个单词,但没有任何迹象表明有一个文件未找到。
pass语句还充当了占位符,它提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么。例如,在这个程序中,我们可能决定将找不到的文件的名称写入到文件missing_files.txt中。用户看不到这个文件,但我们可以读取这个文件,进而处理所有文件找不到的问题。
如果用户知道要分析哪些文件,他们可能希望在有文件没有分析时出现一条消息,将其中的原因告诉他们。如果用户只想看到结果,而并不知道要分析哪些文件,可能就无需在有些文件不存在时告知他们。向用户显示他不想看到的信息可能会降低程序的可用性
10-7. 加法工具
print('Please give me two numbers, and I will add them:')
print('Enter q if you want to quit it')
while True:
first_number=input("Please enter the first number:")
if first_number=='q':
break
second_number=input("Please enter the second number:")
if second_number=='q':
break
try:
answer=int(first_number)+int(second_number)
except ValueError:
print("Please enter two int number.")
break
else:
print(f"The answer is {answer}")
10-8 猫和狗:创建两个文件cats.txt和dogs.txt,在第一个文件中至少存储三只猫的名字,在第二个文件中至少存储三条狗的名字。编写一个程序,尝试读取这些文件,并将其内容打印到屏幕上。将这些代码放在一个try-except代码块中,以便在文件不存在时捕获FileNotFound错误,并打印一条友好的消息。将其中一个文件移到另一个地方,并确认except代码块中的代码将正确地执行。
def open_files(filename):
try:
# 尝试打开并读取传入的文件
with open(filename, 'r') as file:
file_content = file.read()
print(f"{filename}'s content:")
print(file_content)
except FileNotFoundError:
# 如果文件不存在,捕获FileNotFoundError异常,并输出友好的提示消息
print(f"FileNotFoundError: {filename} not found.")
filenames = ['cats.txt', 'dogs.txt']
for filename in filenames:
open_files(filename)
10-9 沉默的猫和狗:修改你在练习10-8中编写的except代码块,让程序在文件不存在时一言不发。
except FileNotFoundError:
# 如果文件不存在,捕获FileNotFoundError异常,并输出友好的提示消息
pass
JSON(JavaScript Object Notation)格式最初是为JavaScript开发的,但随后成了一种常见格式,被包括Python在内的众多语言采用。
import json
numbers=[3,5,7,9,11]
filename='numbers.json'
with open (filename,'w') as f_object:
json.dump(numbers,f_object)
我们先导入模块json,再创建一个数字列表。
我们指定了要将该数字列表存储到其中的文件的名称。通常使用文件扩展名.json来指出文件存储的数据为JSON格式。接下来,我们以写入模式打开这个文件,让json能够将数据写入其中。最后,我们使用函数json.dump()将数字列表存储到文件numbers.json中。这个程序没有输出,但创建了一个numbers.json文件,看看其内容。数据的存储格式与Python中一样。=[3,5,7,9,11]
使用json.load()将这个列表读取到内存中:
import json
filename = 'numbers.json'#1
with open(filename) as f_obj:#2
numbers = json.load(f_obj) #3
print(numbers)
在❶处,我们确保读取的是前面写入的文件。这次我们以读取方式打开这个文件,因为Python只需读取这个文件(见❷)。在❸处,我们使用函数json.load()加载存储在numbers.json中的信息,并将其存储到变量numbers中。最后,我们打印恢复的数字列表,看看它是否与上一个codes中创建的数字列表相同。
【保存和读取用户生成的数据】
对于用户生成的数据,使用json保存它们大有裨益,因为如果不以某种方式进行存储,等程序停止运行时用户的信息将丢失。
[code1]
import json
username=input('What is your name?')
filename = 'username.json'
with open(filename,'w') as f_obj:
json.dump(username,f_obj)
print(f"We'll remember you when you come back {username}.")
[code2]
import json
filename='username.json'
with open(filename) as f_object:#1
username=json.load(f_object)
print(f"Welcome back {username}")
1处
这行代码的作用是打开一个名为 filename 的文件,并将文件对象赋值给变量 f_object。在这个上下文管理器(with 语句块) 内,你可以执行一些文件操作,而且当代码块结束时,Python 会自动关闭文件,即使发生了异常。
import json
filename = 'username.json'#1
try:
with open(filename) as f_object:
username=json.load(f_object)
except FileNotFoundError:#2
username=input('What is your name?')
with open(filename,'w') as f_obj:
json.dump(username,f_obj)
print(f"We'll remember you when you come back {username}.")
else:
print(f"Welcome back,{username}")
1处检查文件是否存在[code2前半]
except 不存在(合并[code1])
else [code2后半]
无论执行的是except代码块还是else代码块,都将显示用户名和合适的问候语。
你经常会遇到这样的情况:代码能够正确地运行,但可做进一步的改进——将代码划分为一系列完成具体工作的函数。这样的过程被称为重构。重构让代码更清晰、更易于理解、更容易扩展。
1. 创建函数
要重构remember_me.py,可将其大部分逻辑放到一个或多个函数中。remember_me.py的重点是问候用户,因此我们将其所有代码都放到一个名为greet_user()的函数中
import json
def greet_user():
'''问候用户,并指出名字'''
filename = 'username.json'
try:
with open(filename) as f_object:
username=json.load(f_object)
except FileNotFoundError:
username=input('What is your name?')
with open(filename,'w') as f_obj:
json.dump(username,f_obj)
print(f"We'll remember you when you come back {username}.")
else:
print(f"Welcome back,{username}")
greet_user()
函数greet_user()所做的不仅仅是问候用户,还在存储了用户名时获取它,而在没有存储用户名时提示用户输入一个。
2.新增储存用户名功能
import json
def get_stored_username():
filename = 'username.json'
try:
with open(filename) as f_object:
username=json.load(f_object)
except FileNotFoundError:
return None
else:
return username
def greet_user():
'''问候用户,并指出其名字'''
username = get_stored_username()
if username:
print(f"Welcome back,{username}")
else:
username=input("What is your name:")
with open(filename,'w') as f_object:
json.dump(username,f_object)
print(f"We will remember you when you come back {username}." )
greet_user()
新增的函数get_stored_username()目标明确,如果存储了用户名,这个函数就获取并返回它;如果文件username.json不存在,这个函数就返回None。
3.终极版细化 每个函数有不同的功能
import json
def get_stored_username():#1
filename = 'username.json'
try:
with open(filename) as f_object:
username=json.load(f_object)
except FileNotFoundError:
return None
else:
return username
def get_new_username():#2
username=input("What is your name:")
with open(filename,'w') as f_object:
json.dump(username,f_object)
return username
def greet_user():#3
'''问候用户,并指出其名字'''
username = get_stored_username()
if username:
print(f"Welcome back,{username}")
else:
username=get_new_username()
print(f"We will remember you when you come back {username}." )
greet_user()
1 检测有没有名字
2 没有的话,建立新名字
3 打招呼 (已有,没有)
10-11 喜欢的数字:编写一个程序,提示用户输入他喜欢的数字,并使用json.dump()将这个数字存储到文件中。再编写一个程序,从文件中读取这个值,并打印消息“I know your favoritenumber! It's _____.”。
import json
def save_favorite_number():
favorite_number = input("请输入你喜欢的数字:")
filename = 'favorite_number.json'
with open(filename, 'w') as file:
json.dump(favorite_number, file)
save_favorite_number()
import json
def load_favorite_number():
filename = 'favorite_number.json'
try:
with open(filename, 'r') as file:
favorite_number = json.load(file)
print(f"I know your favorite number! It's {favorite_number}.")
except FileNotFoundError:
print("抱歉,找不到保存喜欢数字的文件。")
load_favorite_number()
10-12 记住喜欢的数字:将练习10-11中的两个程序合而为一。如果存储了用户喜欢的数字,就向用户显示它,否则提示用户输入他喜欢的数字并将其存储到文件中。运行这个程序两次,看看它是否像预期的那样工作。
def get_favorite_number():
filename = 'favorite_number.json'
try:
with open(filename, 'r') as file:
favorite_number = json.load(file)
print(f"I know your favorite number! It's {favorite_number}.")
except FileNotFoundError:
favorite_number = input("请输入你喜欢的数字:")
with open(filename, 'w') as file:
json.dump(favorite_number, file)
print("已保存你喜欢的数字!")
get_favorite_number()
10-13 验证用户:最后一个remember_me.py版本假设用户要么已输入其用户名,要么是首次运行该程序。
我们应修改这个程序,以应对这样的情形:当前和最后一次运行该程序的用户并非同一个人。为此,在greet_user()中打印欢迎用户回来的消息前,先询问他用户名是否是对的1。如果不对,就调用get_new_username()让用户输入正确的用户名。2
---snip---
def greet_user():
username = get_stored_username()
if username:
correct_user = input(f"Is {username} the correct username? (yes/no): ").lower()
if correct_user == 'yes':
print(f"Welcome back, {username}")
else:
username = get_new_username()
print(f"Welcome back, {username}")
else:
username = get_new_username()
print(f"We will remember you when you come back, {username}.")
greet_user()
在本章中,你学习了:如何使用文件;如何一次性读取整个文件,以及如何以每次一行的方式读取文件的内容;如何写入文件,以及如何将文本附加到文件末尾;什么是异常以及如何处理程序可能引发的异常;如何存储Python数据结构,以保存用户提供的信息,避免用户每次运行程序时都需要重新提供。