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 json
numbers = [2, 3, 5, 7, 11, 13]
filename = 'numbers.json' ❶
with open(filename, 'w') as f:❷
json.dump(numbers, f) ❸
先导入模块json ,再创建一个数字列表。在❶处,指定了要将该数字列表存储到哪个文件中。通常使用文件扩展名.json来指出文件存储的数据为JSON格式。接下来, 以写入模式打开这个文件,让json 能够将数据写入其中(见❷)。在❸处,使用函数json.dump() 将数字列表存储到文件numbers.json中。
这个程序没有输出,但可以打开文件numbers.json来看看内容。数据的存储格式与Python中一样:
[2, 3, 5, 7, 11, 13]
下面再编写一个程序,使用json.load() 将列表读取到内存中:
number_reader.py
import json
numbers = [2, 3, 5, 7, 11, 13]
filename = 'numbers.json' ❶
with open(filename) as f: ❷
number = json.load(f) ❸
print(number)
在❶处,确保读取的是前面写入的文件。这次以读取方式打开该文件,因为Python只需要读取它(见❷)。在❸处,使用函数json.load() 加载存储在numbers.json中的信息,并将其赋给变量numbers 。最后,打印恢复的数字列表,看看是否与number_writer.py中创建的数字列表相同:
[2, 3, 5, 7, 11, 13]
这是一种在程序之间共享数据的简单方式。
10.4.2 保存和读取用户生成的数据
使用json保存用户生成的数据大有裨益,因为如果不以某种方式存储,用户的信息会在程序停止运行时丢失。下面来看一个这样的例子:提示用户首次运行程序时输入自己的名字,并在再次运行程序时记住他。
先来存储用户的名字:
remember_me.py
import json
username = input("What is your name? ")❶
filename = 'username.json'
with open(filename, 'w') as f: ❷
json.dump(username, f) ❸
print(f"We'll remember you when you come back, {username}!")
在❶处,提示输入用户名并将其赋给一个变量。接下来,调用json.dump() ,并将用户名和一个文件对象传递给它,从而将用户名存储到文件中(见❷)。然后,打印一条消息,指出存储了用户输入的信息(见❸):
What is your name? Sun
We'll remember you when you come back, Sun!
现在再编写一个程序,向已存储了名字的用户发出问候:
greet_user.py
import json
filename = 'username.json'
with open(filename) as f:
username = json.load(f) ❶
print(f"Welcome back, {username}!")❷
在❶处,使用json.load() 将存储在username.json中的信息读取到变量username 中。恢复用户名后,就可以欢迎用户回来了(见❷):
Welcome back, Eric!
需要将这两个程序合并到一个程序(remember_me.py)中。这个程序运行时,将尝试从文件username.json中获取用户名。因此,首先编写一个尝试恢复用户名的try代码块。如果这个文件不存在,就在except代码块中提示用户输入用户名,并将其存储到username.json中,以便程序再次运行时能够获取:
remember_me.py
import json
# 如果以前存储了用户名,就加载它。
# 否则,提示用户输入用户名并存储它。
filename = 'username.json'
try:
with open(filename) as f: ❶
username = json.load(f) ❷
except FileNotFoundError: ❸
username = input("What is your name? ") ❹
with open(filename, 'w') as f: ❺
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
else:
print(f"Welcome back, {username}!")
这里没有任何新代码,只是将前两个示例的代码合并到了一个程序中。在❶处,尝试打开文件username.json。如果该文件存在,就将其中的用户名读取到内存中(见❷),再执行else代码块,打印一条欢迎用户回来的消息。用户首次运行该程序时,文件username.json不存在,将引发FileNotFoundError 异常(见❸)。因此Python将执行except 代码块,提示用户输入用户名(见❹),再使用json.dump() 存储该用户名并打印一句问候语(见❺)。
无论执行的是except 还是else 代码块,都将显示用户名和合适的问候语。如果这个程序是首次运行,输出将如下:
What is your name? Eric
We'll remember you when you come back, Eric!
否则,输出将如下:
Welcome back, Eric!
这是程序之前至少运行了一次时的输出。
10.4.3 重构
你经常会遇到这样的情况:代码能够正确地运行,但通过将其划分为一系列完成具体工作的函数,还可以改进。这样的过程称为重构。重构让代码更清晰、更易于理解、更容易扩展。
要重构remember_me.py,可将其大部分逻辑放到一个或多个函数中。remember_me.py的重点是问候用户,因此将其所有代码都放到一个名为greet_user() 的函数中:
remember_me.py
import json
def greet_user():
"""问候用户,并指出其名字。"""❶
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
username = input("What is your name? ")
with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
else:
print(f"Welcome back, {username}!")
greet_user()
考虑到现在使用了一个函数,我们删除原注释,转而使用一个文档字符串来指出程序的作用(见❶)。这个程序更加清晰,但函数greet_user() 所做的不仅仅是问候用户,还在存储了用户名时获取它、在没有存储用户名时提示用户输入。
下面来重构greet_user() ,减少其任务。为此,首先将获取已存储用户名的代码移到另一个函数中:
import json
def get_stored_username():
"""如果存储了用户名,就获取它。"""❶
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
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? ")
filename = 'username.json'
with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
greet_user()
新增的函数get_stored_username() 目标明确,❶处的文档字符串指出了这一点。如果存储了用户名,该函数就获取并返回它;如果文件username.json不存在, 该函数就返回None (见❷)。这是一种不错的做法:函数要么返回预期的值,要么返回None 。这让我们能够使用函数的返回值做简单的测试。在❸处,如果成功地获取了用户名,就打印一条欢迎用户回来的消息,否则提示用户输入用户名。
还需要重构greet_user() 中的另一个代码块,将没有存储用户名时提示用户输入的代码放在一个独立的函数中:
import json
def get_stored_username():
"""如果存储了用户名,就获取它。"""
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
return None
else:
return username
def get_new_username():
"""提示用户输入用户名。"""
username = input("What is your name? ")
filename = 'username.json'
with open(filename, 'w') as f:
json.dump(username, f)
return username
def greet_user():
#问候用户,并指出其名字。
username = get_stored_username()
if username:
print(f"Welcome back,{username}!")
else:
username = get_new_username()
print(f"We'll remember you when you come back,{username}!")
greet_user()
在remember_me.py的这个最终版本中,每个函数都执行单一而清晰的任务。我们调用greet_user() ,它打印一条合适的消息:要么欢迎老用户回来,要么问候新用户。为此,它首先调用get_stored_username() ,该函数只负责获取已存储的用户名(如果存储了的话)。最后在必要时调用get_new_username() ,该函数只负责获取并存储新用户的用户名。要编写出清晰而易于维护和扩展的代码,这种划分必不可少。
动手试一试
练习10-11:喜欢的数
编写一个程序,提示用户输入喜欢的数,并使用json.dump() 将这个数存储到文件中。再编写一个程序,从文件中读取这个值,并打印如下所示的消息。
I know your favorite number! It's _____.
import json
filename = 'favorite_number.json'
number = input("请输入一个你喜欢的数字:")
with open(filename,'w') as f:
json.dump(number,f)
with open(filename) as f:
favorite_number = json.load(f)
print(f"I know your favorite number. It's {favorite_number}.")
练习10-12:记住喜欢的数
将练习10-11中的程序合二为一。如果存储了用户喜欢的数,就向用户显示它,否则提示用户输入喜欢的数并将其存储到文件中。运行这个程序两次,看看它能否像预期的那样工作。
import json
filename = 'favorite_number.json'
try:
with open(filename) as f:
favorite_number = json.load(f)
except FileNotFoundError:
favorite_number = input("请输入一个你喜欢的数字:")
with open(filename,'w') as f:
json.dump(favorite_number,f)
print(f"I know your favorite number. It's {favorite_number}.")
练习10-13:验证用户
最后一个remember_me.py版本假设用户要么已输入用户名,要么是首次运行该程序。我们应该修改这个程序,以防当前用户并非上次运行该程序的用户。
为此,在greet_user() 中打印欢迎用户回来的消息前,询问他用户名是否正确。如果不对,就调用get_new_username() 让用户输入正确的用户名。
import json
def get_stored_username():
#如果存储了用户名,就获取它。
filename = 'username.json'
try:
with open(filename) as f:
username = json.load(f)
except FileNotFoundError:
return None
else:
confirmed = input(f"Is your name {username}?(yes/no)")
if confirmed == "no":
username = input("Please input your name. ")
return username
def get_new_username():
#提示用户输入用户名。
username = input("What's your name? ")
filename = 'username.json'
with open(filename,'w') as f:
json.dump(username,f)
return username
def greet_user():
#问候用户,并指出其名字。
username = get_stored_username()
if username:
print(f"Welcome back,{username}!")
else:
username = get_new_username()
print(f"We'll remember you when you come back,{username}!")
greet_user()
10.5 小结
在本章中,你学习了:
如何使用文件;
如何一次性读取整个文件,以及如何以每次 一行的方式读取文件的内容;
如何写入文件,以及如何将文本附加到文件末尾;
什么是异常以及如何处理程序可能引发的异常;
如何存储Python数据结构,以保存用户提供的信息,避免用户每次运行程序时都需要重新提供。