Python3完全新手小白的学习手册 10 文件和异常
文章目录
- 读取文件
- 读取文件的全部内容
- 相对路径和绝对路径
- 访问文件中的各行
- 使用文件的内容
- 包含100万位的大型文件
- 圆周率值中包含你的生日吗?
- 写入文件
- 写入一行
- 写入多行
- 异常
- 处理ZeroDivisionError异常
- 使用try-except代码块
- else代码块
- 处理FileNotFoundError异常
- 分析文本
- 使用多个文件
- 静默失败
- 决定报告哪些错误
- 存储数据
- 使用json.dump()和json.load()
- 保存和读取用户生成的数据
- 重构
- 往期
- 代码仓库
从第一到第十章,我们学习了编写程序的所需的基本技能。
在本章节中,我们将学习如何处理文件,以及如何使用异常处理错误。
读取文件
文本文件可以存储许多数据:天气数据、交通数据、社会经济数据、文学作品等。
要使用文本文件中的信息,首先需要将信息读取到内存中。既可以一次性读取文件的全部内容,也可以逐行读取。
读取文件的全部内容
我们先准备一个文件,名为pi_digits.txt,内容如下:
3.141592653589793238462643383279
然后我们开始编写第一个读取文件的代码file_reader.py:
from pathlib import Pathpath = Path('pi_digits.txt')
contents = path.read_text()
print(contents)
要使用文件的内容,需要将其路径告知 Python。
路径(path)是文件或文件夹在系统中的准确位置。
Python 提供了 pathlib 模块,能处理各种操作系统中处理文件和目录。
首先中 pathlib 模块导入 Path 类,再使用 Path 对像指向一个文件。
这里创建了一个表示文件 pi_digits.txt 的 Path 对象,并将其赋给了变量 path由于这个文件与当前编写的 .py 文件位于同一个目录中,因此 Path 只需要知道其文件名就能访问它。
创建表示文件 pi_digits.txt 的 Path 对象后,使用 read_text() 方法来读取这个文件的全部内容。
**read_text()**将该文件的全部内容作为一个字符串返回。我们将这个字符串赋给了变量 contents,再将其打印出来。
相比原始原件,输出唯一不同的地方是末尾多了一个空行。
因为 read_text()在到达文件末尾时会返回一个空字符串,而这个空字符串会被显示一个空行。
要删除这个多出来的空行,可对字符串变量contents调用 strip() 方法。
contents = path.read_text().rstrip()
这种方式称为方法链式调用。
博主使用的 3.13版本已经没有了这个问题。
相对路径和绝对路径
指定路径的方式有两种:
- 相对文件路径让 Python 到相对于当前运行的程序文件所在的目录去查找。
- 绝对文件路径让 Python 去系统的准确位置去查找。
在相对路径行不通时,可使用绝对路径。
绝对路径通常比相对路径长,因为以系统的根文件夹为起点。
在 Linux 系统中,系统的根文件夹为 /,而在 Windows 系统中,系统的根文件夹为 C:\。
path = Path('/home/eric/data_files/text_files/filename.txt')
在 Windows 系统中,可使用反斜杠(\)而不是斜杠(/)来分隔路径中的文件夹。
访问文件中的各行
使用 splitlines() 方法将冗长的字符串转换为一系列行,再使用 for 循环以每次一行的方式检查文件中的各行
from pathlib import Pathpath = Path('pi_digits.txt')
contents = path.read_text()lines = contents.splitlines()
for line in lines:print(line)
和前面一样,读取文件的全部内容,由于没有修改行,因此输出与文件内容相同。
使用文件的内容
将文件的内容读取到内存中后,就可以以任何方式使用这些数据了。
首先,创建一个字符串,它包含文件中存储的所有数字。
from pathlib import Pathpath = Path('pi_digits.txt')
contents = path.read_text()lines = contents.splitlines()
# 创建变量 pi_string
pi_string = ''
for line in lines:pi_string += lineprint(pi_string)
print(len(pi_string))
输出如下:
3.1415926535 8979323846 2643383279
32
变量pi_string存储的字符串包含原来位于每行左端的空格。要删除这些空格,可对每行调用lstrip()方法。
for line in lines:pi_string += line.lstrip()
输出如下:
3.141592653589793238462643383279
32
注意:在读取文本文件时,Python将其中的所有文本都解释为字符串。
如果读取的是数字,并要将其作为数值使用,就必须使用函数 int() 将其转换为整数,或使用函数 float() 将其转换为浮点数。
包含100万位的大型文件
如果一个文本文件包含精确到小数点后 1 000 000 位而不是 30 位的圆周率值,也可以创建一个包含所有这些数字的字符串。
在这个例子中,我们将使用一个包含 100 000 位的圆周率值的文本文件,该文件的第一行只包含前 50 位。
from pathlib import Pathpath = Path('pi_million_digits.txt')
contents = path.read_text()
lines = contents.splitlines()
pi_string = ''
for line in lines:pi_string += line.lstrip()print(f"{pi_string[:52]}...")
print(len(pi_string))
圆周率值中包含你的生日吗?
现在我们知道如何使用 Python 来读取文件,我们来编写一个程序,看看圆周率值中包含你的生日吗?
from pathlib import Pathpath = Path('pi_million_digits.txt')
contents = path.read_text()
lines = contents.splitlines()
pi_string = ''
for line in lines:pi_string += line.lstrip()
birthday = input("Enter your birthday, in the form mmddyy: ")
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.")
写入文件
保存数据的最简单的方式之一是将其写入文件。
写入一行
定义一个文件的路径后,就可使用 write_text() 将数据写入该文件了。
from pathlib import Pathpath = Path("programming.txt")
# write_text() 方法接受单个实参,即要写入文件的字符串。
path.write_text("I love programming.\n")
。这个程序没有终端输出,但你如果打开文件 programming.txt。
注意:Python 只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数 str() 将其转换为字符串格式。
写入多行
write_text() 方法会在幕后完成几项工作。
首先,如果 path 变量对应的路径指向的文件不存在,就创建它。
其次,将字符串写入文件后,它会确保文件得以妥善地关闭。
要将多行写入文件,需要先创建一个字符串(其中包含要写入文件的全部内容),再调用 write_text() 并将这个字符串传递给它。
from pathlib import Path
contents = "I love programming.\n"
contents += "I love creating new games.\n"
contents += "I also love working with data.\n"path = Path('programming.txt')
path.write_text(contents)
在对 path 对象调用 write_text() 方法时,务必谨慎。如果指定的文件已存在, write_text() 将删除其内容,再将指定的文本写入其中。
异常
Python 使用称为异常(exception)的特殊对象来管理程序执行期间发生的错误。
异常是使用 try-except 代码块处理的。
处理ZeroDivisionError异常
print(5/0)
输出如下:
Traceback (most recent call last):File "C:\Users\Administrator\PycharmProjects\pythonProject\demo.py", line 1, in <module>print(5/0) # 报错
ZeroDivisionError: division by zero
在上述 traceback 中,错误 ZeroDivisionError 是个异常对象。
Python 在无法按你的要求做时,就会创建这种对象。
使用try-except代码块
try:print(5/0)
except ZeroDivisionError:print("You can't divide by zero!")
输出如下:
You can't divide by zero!
当你认为可能发生了错误时,可编写一个 try 语句来告诉 Python,你要执行的操作可能引发这种错误。
else代码块
只有 try 代码块成功执行才需要继续执行的代码,都应放到 else 代码块中
--snip - -
while True:--snip - -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)
如果除法运算成功,就使用 else 代码块来打印结果
处理FileNotFoundError异常
在使用文件时,一种常见的问题是找不到文件:要查找的文件可能在其他地方,文件名可能不正确,或者这个文件根本就不存在。
from pathlib import Pathpath = Path('alice.txt')
contents = path.read_text(encoding='utf-8')
这里使用 read_text() 的方式与前面稍有不同。如果系统的默认编码与要读取的文件的编码不一致,参数 encoding 必不可少。
Python 无法读取不存在的文件,因此引发了一个异常FileNotFoundError
通常最好从 traceback 的末尾着手。从最后一行可知,引发了异常 FileNotFoundError。
from pathlib import Pathpath = Path('alice.txt')
try:contents = path.read_text(encoding='utf-8')
except FileNotFoundError:print(f"Sorry, the file {path} does not exist.")
这个示例中,try 代码块中的代码引发了 FileNotFoundError 异常,因此要编写一个与该异常匹配的 except 代码块。
这样,当找不到文件时,Python 将运行 except 代码块中的代码,从而显示一条友好的错误消息,而不是 traceback。
分析文本
from pathlib import Pathpath = Path('alice.txt')
try:contents = path.read_text(encoding='utf-8')
except FileNotFoundError:print(f"Sorry, the file {path} does not exist.")
else:# 计算文件大致包含多少个单词words = contents.split()num_words = len(words)print(f"The file {path} has about {num_words} words.")
仅当 try 代码块成功执行时才会执行它们。输出指出了文件 alice.txt 包含多少个单词。
使用多个文件
下面分析几本书
from pathlib import Pathdef count_words(filename):"""计算一个文件大致包含多少个单词。"""try:contents = Path(filename).read_text(encoding='utf-8')except FileNotFoundError:msg = f"Sorry, the file {filename} does not exist."print(msg)else:# 计算文件大致包含多少个单词。words = contents.split()num_words = len(words)print(f"The file {filename} has about {num_words} words.")filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:count_words(filename)
先将文件名存储为简单字符串,然后将每个字符串转换为 Path 对象,再调用 count_words()。
在这个示例中,使用 try-except 代码块有两个重要的优点:一是避免用户看到 traceback,二是让程序可以继续分析能够找到的其他文件。
静默失败
Python 有一个 pass 语句,可在代码块中使用它来让 Python 什么都不做。
def count_words(path):"""计算一个文件大致包含多少个单词"""try:--snip--except FileNotFoundError:passelse:--snip--
当这种错误发生时,既不会出现 traceback,也没有任何输出。用户将看到存在的每个文件包含多少个单词,但没有任何迹象表明有一个文件未找到
决定报告哪些错误
只要程序依赖于外部因素,如用户输入、是否存在指定的文件、是否有网络连接,就有可能出现异常。凭借经验可判断该在程序的什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息。
存储数据
一种简单的方式是使用模块 json 来存储数据。
使用json.dump()和json.load()
第一个程序将使用 json.dumps() 来存储这组数,而第二个程序将使用 json.loads() 来读取它们。
from pathlib import Path
import jsonnumbers = [2,3,5,7,11,13]path = Path('numbers.json')
contents = json.dumps(numbers)
path.write_text(contents)
首先导入模块 json,并创建一个数值列表。然后选择一个文件名,指定要将该数值列表存储到哪个文件中。
接下来,使用 json.dumps() 函数生成一个字符串,将其存储到变量 contents 中。
最后,使用 Path 对象的 write_text() 方法将这个字符串写入到文件 numbers.json 中。
使用 json.loads() 将这个列表读取到内存中
from pathlib import Path
import jsonpath = Path('numbers.json')
contents = path.read_text()
numbers = json.loads(contents)
print(numbers)
这个数据文件是使用特殊格式的文本文件,因此可使用 read_text() 方法来读取它。然后将这个文件的内容传递给 json.loads()。这个函数将一个 JSON 格式的字符串作为参数,并返回一个 Python 对象(这里是一个列表),而我们将这个对象赋给了变量 numbers。最后,打印恢复的数值列表
保存和读取用户生成的数据
使用 json 保存用户生成的数据很有必要
from pathlib import Path
import json# 首先,提示用户输入名字
username = input("请输入你的用户名:")# 接下来,将收集到的数据写入文件 username.json
path = Path('username.json')
contents = json.dump(username)
path.write_text(contents)# 然后,打印一条消息,指出存储了用户输入的信息
print(f"我们将记住你的用户名,{username}")
再编写一个程序,向名字已被存储的用户发出问候
from pathlib import Path
import jsonpath = Path('username.json')
contents = path.read_text()
username = json.loads(contents)print(f"欢迎回来,{username}!")
我们读取数据文件的内容,并使用 json.loads() 将恢复的数据赋给变量 userusername
from pathlib import Path
import jsonpath = Path('username.json')
# 如果指定的文件或文件夹存在,exists() 方法返回 True,否则返回 False。
# 这里使用 path.exists() 来确定是否存储了用户名
if path.exists():# 如果文件 username.json 存在,就加载其中的用户名,并向用户发出个性化问候contents = path.read_text()username = json.loads(contents)print(f"欢迎回来,{username}!")
else:# 如果文件 username.json 不存在,就提示用户输入用户名,并存储用户输入的值。username = input("请输入你的用户名:")contents = json.dumps(username)path.write_text(contents)print(f"我们将记住你的用户名,{username}")
重构
虽然代码能够正确地运行,但还可以将其划分为一系列完成具体工作的函数来进行改进。这样的过程称为重构。
重构让代码更清晰、更易于理解、更容易扩展。
from pathlib import Path
import jsondef greet_user():"""问候用户,并指出其名字。"""path = Path('users.json')if path.exists():contents = path.read_text()username = json.loads(contents)print(f'欢迎回来{username}')else:username = input('请输入你的名字:')contents = json.dumps(username)path.write_text(contents)print(f'我们将记住你的名字{username}')greet_user()
greet_user() 函数所做的不仅是问候用户,还在存储了用户名时获取它,在没有存储用户名时提示用户输入。
下面重构 greet_user(),不让它执行这么多任务。
from pathlib import Path
import json# 如果存储了用户名,就获取并返回它;如果传递给 get_stored_username() 的路径不存在,就返回 None
def get_stored_username(path):"""如果存储了用户名,就获取它"""if path.exists():contents = path.read_text()username = json.loads(contents)return usernameelse:# 这是一种不错的做法:函数要么返回预期的值,要么返回 None。return Nonedef get_stored_username(path):"""提示用户输入用户名"""username = input('请输入你的名字? ')contents= json.dumps(username)path.write_text(contents)return usernamedef greet_user():"""问候用户,并指出其名字"""path = Path('username.json')username = get_stored_username(path)if username:print(f"Welcome back, {username}!")else:username = get_stored_username(path)print(f"We'll remember you when you come back, {username}!")greet_user()
在 remember_me.py 的这个最终版本中,每个函数都执行单一而清晰的任务。我们调用 greet_user(),它打印一条合适的消息:要么欢迎老用户回来,要么问候新用户。
首先调用 get_stored_username(),这个函数只负责获取已存储的用户名(如果存储了),再在必要时调用 get_new_username(),这个函数只负责获取并存储新用户的用户名。要编写出清晰且易于维护和扩展的代码,这种划分必不可少。
往期
- Python3完全新手小白的学习手册 9 类
- Python3完全新手小白的学习手册 8 函数
- Python3完全新手小白的学习手册 7 用户输入和while循环
- Python3完全新手小白的学习手册 6 字典
- Python3完全新手小白的学习手册 5 if语句
- Python3完全新手小白的学习手册 4 操作列表
- Python3完全新手小白的学习手册 3 列表
- Python3完全新手小白的学习手册 2 变量和简单数据类型
- Python3完全新手小白的学习手册 1 Python 的安装
代码仓库
代码仓库