存储数据
您的许多程序都会要求用户输入某些类型的信息。 您可能允许用户在游戏中存储偏好或为可视化提供数据。 无论您的程序的重点是什么,您都会将用户提供的信息存储在列表和字典等数据结构中。 当用户关闭程序时,您几乎总是希望保存他们输入的信息。 一种简单的方法是使用 json 模块存储数据。
json 模块允许您将简单的 Python 数据结构转换为 JSON 格式的字符串,然后在程序下次运行时从该文件加载数据。 您还可以使用 json 在不同的 Python 程序之间共享数据。 更好的是,JSON 数据格式并非 Python 所特有,因此您可以与使用许多其他编程语言的人共享以 JSON 格式存储的数据。 这是一种有用且可移植的格式,而且很容易学习。
JSON(JavaScript 对象表示法)格式最初是为 JavaScript 开发的。 然而,它已经成为包括 Python 在内的许多语言使用的通用格式。 |
使用json.dumps()和json.loads()
让我们编写一个存储一组数字的短程序和另一个将这些数字读回内存的程序。 第一个程序将使用 json.dumps() 来存储数字集,第二个程序将使用 json.loads()。
json.dumps() 函数接受一个参数:一段应该转换为 JSON 格式的数据。 该函数返回一个字符串,然后我们可以将其写入数据文件:
from pathlib import Path
import json
numbers = [2, 3, 5, 7, 11, 13]
path = Path('numbers.json') (1)
contents = json.dumps(numbers) (2)
path.write_text(contents)
我们首先导入 json 模块,然后创建要使用的数字列表。 然后我们选择一个文件名来存储数字列表❶。 通常使用文件扩展名.json来表示文件中的数据以JSON格式存储。 接下来,我们使用 json.dumps() ❷ 函数生成一个字符串,其中包含我们正在处理的数据的 JSON 表示形式。 一旦我们有了这个字符串,我们就使用我们之前使用的相同的 write_text() 方法将它写入文件。
该程序没有输出,但让我们打开文件 numbers.json 并查看它。 数据以类似于 Python 的格式存储:
[2, 3, 5, 7, 11, 13]
现在我们将编写一个单独的程序,使用 json.loads() 将列表读回内存:
from pathlib import Path
import json
path = Path('numbers.json') (1)
contents = path.read_text() (2)
numbers = json.loads(contents) (3)
print(numbers)
我们确保从写入 ❶ 的同一个文件中读取。 由于数据文件只是一个具有特定格式的文本文件,我们可以使用 read_text() 方法读取它 ❷。 然后我们将文件的内容传递给 json.loads() ❸。 此函数接受一个 JSON 格式的字符串并返回一个 Python 对象(在本例中为列表),我们将其分配给数字。 最后,我们打印恢复的数字列表,发现它与在 number_writer.py 中创建的列表相同:
[2, 3, 5, 7, 11, 13]
这是在两个程序之间共享数据的简单方法。
保存和读取用户生成的数据
当您处理用户生成的数据时,使用 json 保存数据很有用,因为如果您不以某种方式存储您的用户信息,当程序停止运行时您将丢失它。 让我们看一个例子,我们在用户第一次运行程序时提示他们输入他们的名字,然后在他们再次运行该程序时记住他们的名字。
让我们从存储用户名开始:
from pathlib import Path
import json
username = input("What is your name? ") (1)
path = Path('username.json') (2)
contents = json.dumps(username)
path.write_text(contents)
print(f"We'll remember you when you come back, {username}!") (3)
我们首先提示输入用户名来存储❶。 接下来,我们将刚刚收集到的数据写入到一个名为 username.json 的文件中。 然后我们打印一条消息,通知用户我们已经存储了他们的信息❸:
What is your name? Eric
We'll remember you when you come back, Eric!
现在让我们编写一个新程序来问候一个名字已经被存储的用户:
from pathlib import Path
import json
path = Path('username.json') (1)
contents = path.read_text()
username = json.loads(contents) (2)
print(f"Welcome back, {username}!")
我们读取数据文件的内容❶,然后使用 json.loads() 将恢复的数据赋值给变量用户名❷。 由于我们已经恢复了用户名,我们可以用个性化的问候语欢迎用户回来:
Welcome back, Eric!
我们需要将这两个程序合并到一个文件中。 当有人运行 remember_me.py 时,我们希望尽可能从内存中检索他们的用户名; 如果没有,我们将提示输入用户名并将其存储在 username.json 中以供下次使用。 如果 username.json 不存在,我们可以在这里编写一个 try-except 块来做出适当的响应,但是我们将使用 pathlib 模块中的一个方便的方法:
from pathlib import Path
import json
path = Path('username.json')
if path.exists(): (1)
contents = path.read_text()
username = json.loads(contents)
print(f"Welcome back, {username}!")
else: (2)
username = input("What is your name? ")
contents = json.dumps(username)
path.write_text(contents)
print(f"We'll remember you when you come back, {username}!")
有许多有用的方法可用于 Path 对象。 如果文件或文件夹存在,则 exists() 方法返回 True,否则返回 False。 这里我们使用 path.exists() 来查明用户名是否已经被存储了❶。 如果 username.json 存在,我们加载用户名并向用户打印个性化问候语。
如果文件 username.json 不存在,我们会提示输入用户名并存储用户输入的值。 我们还打印了熟悉的消息,即当他们回来时我们会记住他们。
无论执行哪个块,结果都是用户名和适当的问候语。 如果这是程序第一次运行,这是输出:
What is your name? Eric
We'll remember you when you come back, Eric!
否则:
Welcome back, Eric!
这是您在程序至少运行过一次时看到的输出。 即使本节中的数据只是一个字符串,该程序也可以处理任何可以转换为 JSON 格式字符串的数据。
重构
通常,您会到达代码可以正常工作的地步,但您会认识到可以通过将代码分解为一系列具有特定工作的函数来改进代码。 这个过程称为重构。 重构使您的代码更清晰、更易于理解并且更易于扩展。
我们可以通过将其大部分逻辑移动到一个或多个函数中来重构 remember_me.py。 remember_me.py 的重点是问候用户,所以让我们将所有现有代码移到一个名为 greet_user() 的函数中:
from pathlib import Path
import json
def greet_user():
"""Greet the user by name.""" (1)
path = Path('username.json')
if path.exists():
contents = path.read_text()
username = json.loads(contents)
print(f"Welcome back, {username}!")
else:
username = input("What is your name? ")
contents = json.dumps(username)
path.write_text(contents)
print(f"We'll remember you when you come back, {username}!")
greet_user()
因为我们现在正在使用一个函数,所以我们将注释重写为一个文档字符串,以反映程序当前的工作方式❶。 这个文件更干净一些,但是函数 greet_user() 不仅仅是问候用户——它还检索存储的用户名(如果存在)并提示输入新用户名(如果不存在)。
让我们重构 greet_user(),这样它就不会执行那么多不同的任务。 我们将从将用于检索存储的用户名的代码移动到一个单独的函数开始:
from pathlib import Path
import json
def get_stored_username(path):
"""Get stored username if available.""" (1)
if path.exists():
contents = path.read_text()
username = json.loads(contents)
return username
else:
return None (2)
def greet_user():
"""Greet the user by name."""
path = Path('username.json')
username = get_stored_username(path)
if username: (3)
print(f"Welcome back, {username}!")
else:
username = input("What is your name? ")
contents = json.dumps(username)
path.write_text(contents)
print(f"We'll remember you when you come back, {username}!")
greet_user()
新函数 get_stored_username() ❶ 有一个明确的目的,如文档字符串中所述。 此函数检索存储的用户名,如果找到则返回用户名。 如果传递给 get_stored_username() 的路径不存在,函数返回 None ❷。 这是一个很好的做法:一个函数要么返回你期望的值,要么返回 None。 这允许我们用函数的返回值执行一个简单的测试。 如果检索用户名的尝试成功,我们会向用户打印一条欢迎返回消息,如果不成功,我们会提示输入新的用户名。
我们应该从 greet_user() 中再分解出一段代码。 如果用户名不存在,我们应该将提示输入新用户名的代码移至专用于该目的的函数中:
from pathlib import Path
import json
def get_stored_username(path):
"""Get stored username if available."""
if path.exists():
contents = path.read_text()
username = json.loads(contents)
return username
else:
return None
def get_new_username(path):
"""Prompt for a new username."""
username = input("What is your name? ")
contents = json.dumps(username)
path.write_text(contents)
return username
def greet_user():
"""Greet the user by name."""
path = Path('username.json')
username = get_stored_username(path) (1)
if username:
print(f"Welcome back, {username}!")
else:
username = get_new_username(path) (2)
print(f"We'll remember you when you come back, {username}!")
greet_user()
remember_me.py 最终版本中的每个函数都有一个明确的目的。 我们调用 greet_user(),该函数会打印一条适当的消息:它欢迎现有用户回来或问候新用户。 它通过调用 get_stored_username() ❶ 来做到这一点,它只负责检索存储的用户名(如果存在)。 最后,如果有必要,greet_user() 会调用 get_new_username()❷,它只负责获取新的用户名并存储它。 这种工作划分是编写易于维护和扩展的清晰代码的重要组成部分。