10 文件和异常
10.1 读写文件
10.1.1 文件路径
文件路径的表示有两种方法:绝对路径表示法和相对路径表示法。
1、绝对文件路径
绝对文件路径是指文件在硬盘上存储的真正路径。它不会被更改,除非文件在硬盘中的位置发生改变。
表示格式:‘<硬盘符>/<目录1>/<目录2>…/<文件名>’
例:‘C:/path/to/file.txt’
绝对路径往往较长,可以把它赋给一个变量。
file_path = 'C:/path/to/file.txt'
2、相对文件路径
相对文件路径是指从当前文件位置出发,指向目标文件的路径。它是目标文件与当前文件的相对位置,即使目标文件的位置不变,它也会随着当前文件位置改变而改变。
表示格式:‘./<目录1>/<目录2>…/<文件名>’
注意:
./ 表示当前目录,可以省略
../ 表示父级路径,即当前路径所在的上一级路径。(仅了解)
例:
①若当前项目在path文件夹中,则file的相对路径为’./to/file.txt’或’to/file.txt’。
②若当前项目在to文件夹中,即与’file.txt’在同一文件夹,则file的相对路径就是文件名’file.txt’。
③若当前项目在to文件夹中,需要使用的文件’file2.txt’在C盘根目录下,则file2的相对路径为’../../file2.txt’。(仅了解)
使用相对路径时,也可以把它赋给一个变量。
file_path = 'to/file.txt'
总注:
1、不同盘符间的文件不能用相对路径来表示,例如位于C盘下的文件无法使用相对路径来表示D盘中的文件。
2、显示文件路径时,Windows系统使用的是反斜杠(\)而非(/),但在代码中往往使用斜杠(/)。如过在代码中使用反斜杠(\),会导致Python将其理解为转义字符,从而引发错误,如果非要使用反斜杠(\),则需要先对每个反斜杠进行转义,即表示为
'C:\\path\\to\\file.txt'
10.1.2 打开文件
请前往 ituring.cn/book/2784 ,点击右侧第四栏“随书下载”,选择下载“源代码文件.zip”,里面包含后续部分所需的相关文本文件。
可以运用 "with open(…) as … "结构打开文件。这个结构包含两个要点,第一,函数open()可以接受一个参数,即文件路径,返回一个表示文件的对象,可以将其赋给一个变量供以后使用。第二,关键字with在不需要访问文件后自动将其关闭。
file_path = 'pi_digits.txt'
with open(file_path) as file_object:
print(file_object)
<_io.TextIOWrapper name='pi_digits.txt' mode='r' encoding='cp936'>
我们可以看到,这样打印的并不是文件内容,而是关于文件的信息,但这也说明我们成功打开了文件。
注:其实,也可以调用open()函数和close()函数来打开和关闭文件,但是如果程序存在bug导致close()函数未执行,文件将不会被关闭,这可能会导致数据受损或丢失。另外,如果过早的调用close()函数,会导致需要使用文件时发现文件已经被关闭的情形发生。因此,不要使用这种方法。
10.1.3 读取文件
↓ pi_digits.txt
3.1415926535
8979323846
2643383279
这是一个4行的文件,包含小数点后30位圆周率,且小数点后每10位处进行一次换行。
1、读取整个文件(read()方法)
方法read()可以读取文件内容,并返回一个长长的字符串。需要注意的是,使用关键字with的时候,open()函数返回的文件只在with代码块内可用,如果要在代码块外访问文件的内容,可以将文件读取后存储在变量中,方便关闭文件后继续使用文件的内容。
file_path = 'pi_digits.txt'
with open(file_path) as file_object:
content = file_object.read() #此处把文件读取后存储在变量content中
print(content)
3.1415926535
8979323846
2643383279
输出末尾出现了一个空行,可以使用rstrip()方法删除字符串末尾的空白。
file_path = 'pi_digits.txt'
with open(file_path) as file_object:
content = file_object.read()
print(content.rstrip())
3.1415926535
8979323846
2643383279
复习:strip()、lstrip()、rstrip(),可以含有一个字符类型的参数,缺省默认为空格;strip():删除string字符串开头和末尾的指定字符,lstrip():删除string字符串开头的指定字符(默认为空格),rstrip():删除string字符串末尾的指定字符(默认为空格)。
2、逐行读取
读取文件时,常常需要检查其中的每一行,可能要在文件中查找特定信息,也可能要以某种方式修改文件中的文本。要以每次一行的方式检查文件,可以对文件对象使用for循环。
file_path = 'pi_digits.txt'
with open(file_path) as file_object:
for line in file_object:
print(line)
3.1415926535
8979323846
2643383279
可以看到打印的结果中,空白行变多了,这是因为在文件中每行的末尾都有一个看不见的换行符,因此每行打印的结果都会多出一个空行。同样,可以使用rstrip()方法删除这些空行。
file_path = 'pi_digits.txt'
with open(file_path) as file_object:
for line in file_object:
print(line.rstrip())
3.1415926535
8979323846
2643383279
3、逐行读取并存储为列表(readlines()方法)
与1中类似,需要在with代码块内将文件的各行存储在一个列表中。与1、2中类似,可以结合使用rstrip()方法。
file_path = 'pi_digits.txt'
with open(file_path) as file_object:
lines = file_object.readlines()
for line in lines:
print(line.rstrip())
3.1415926535
8979323846
2643383279
【实例】圆周率中包含你的生日吗?
本例使用下载的 pi_million_digits.txt 文件,其中包含圆周率的前1000000位。
首先将文件按行储存在列表中,再将列表拼接成整洁的string类型的圆周率,检查字符串的长度是否为1000002。
file_path = 'pi_million_digits.txt'
with open(file_path) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
print(f"圆周率小数点后50位为:{pi_string[0:52]}")
print(len(pi_string))
圆周率小数点后50位为:3.14159265358979323846264338327950288419716939937510
1000002
然后提示用户输入生日,再检查用户的生日是否在圆周率中,打印相关结果。
birthday = input("请以yymmdd的格式输入你的生日:")
if birthday in pi_string:
print("圆周率中包含你的生日")
else:
print("小数点后一百万位圆周率中不包含你的生日")
请以yymmdd的格式输入你的生日:960518
圆周率中包含你的生日
10.1.4 写入文件
之前介绍了用于打开文件的open()函数,实际上它可以接受两个参数,第一个参数就是前面一直在用的文件路径,第二个参数是告诉Python以怎样的模式打开文件。打开文件的模式包括
模式名称 | 参数 | 功能 | 文件不存在的处理方式 | 写入方式 |
读取模式 | r | 只能读取 | 报错 | —— |
r+ | 可读可写 | 报错 | 覆盖 | |
写入模式 | w | 只能写入 | 创建 | 覆盖 |
w+ | 可读可写 | 创建 | 覆盖 | |
附加模式 | a | 只能写入 | 创建 | 追加 |
a+ | 可读可写 | 创建 | 追加 |
注:
①类似的参数还有‘rb’、‘wb’,它们分别指的是以二进制方式读取、写入。
②写入文本时,需要结合使用文件对象的write()方法。
③Python只能将字符串写入文件中,如果要将数值存储到文本文件中,必须先使用函数str()将其强制转换为字符串格式。
下面举例说明写入模式和附加模式的应用。首先尝试写入模式下,创建一个txt文件,写入一句话。
with open('new_text.txt','w') as file_object:
file_object.write("I love programming.")
这个程序没有输出,但是我们可以在程序所在位置发现新创建的txt文件,并且写入了相关内容。
下面尝试在这个文件中写入两句话,
with open('new_text.txt','w') as file_object:
file_object.write("I really love programming.")
file_object.write("I also love creating new games")
可以看出,第一,新的内容覆盖了原有内容;另外,两行内容挤在一起,需要添加转义字符"\n"进行换行。
with open('new_text.txt','w') as file_object:
file_object.write("I really love programming.\n")
file_object.write("I also love creating new games.\n")
如果我们想在这个文件后,添加一些内容,则可以使用附加模式。
with open('new_text.txt','a') as file_object:
file_object.write("I love creating apps that can run in a browser.\n")
10.2 异常
异常是Python创建的特殊对象,用于管理程序运行时出现的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象。如果编写了处理异常的代码,则程序将继续运行,否则程序将停止运行并显示包含有关异常报告的traceback。
常见的异常举例:
1、ZeroDivisionError
当运算中出现0做除数时,会引发ZeroDivisionError(除零异常)。
2、FileNotFoundError
使用文件时,如果找不到文件,就会引发FileNotFoundError,这可能是文件名错误(包括后缀)、路径错误或文件不存在造成的。
10.2.1 try-except代码块
当可能发生错误时,可以编写一个try-except代码块来处理可能引发的异常。如果try代码块中的代码运行起来没有问题,则except代码块会被跳过;如果try代码块中的代码导致了错误,则Python会查找与之匹配的except代码块并运行其中的代码。
try:
print(5/0)
except ZeroDivisionError:
print("0不能做除数")
0不能做除数
可以看到,本例中,try代码块中的代码引发了ZeroDivisionError异常,并且存在与之对应except代码块,因此Python运行了该except代码块。这样,用户看到的是一条友好的错误提示,而非traceback。
另外,如果在except后面不带任何异常,则发生任何异常都将被except代码块捕获,但通常不建议这么用,因为这不能让用户识别出具体的异常信息。
10.2.2 try-except-else代码块
try-except-else代码块中,如果try代码块执行成功,则会继续执行else代码块中的内容。
print("请输入两个数,将得到它们相除的结果.\n输入'q'退出程序.")
while True:
first_number = input("第一个数:")
if first_number == 'q':
break
second_number = input("第二个数:")
if second_number == 'q':
break
try:
answer = float(first_number)/float(second_number)
except ZeroDivisionError:
print("零不能做除数。")
except ValueError:
print("请确保输入为两个数字。")
else:
print(answer)
请输入两个数,将得到它们相除的结果.
输入'q'退出程序.
第一个数:w
第二个数:2
请确保输入为两个数字。
第一个数:1
第二个数:2
0.5
第一个数:4
第二个数:0
零不能做除数。
第一个数:q
10.2.3 静默失败
在实际应用中,并非每次捕获到异常都要告诉用户,有时候需要程序在发生异常时保持静默。要让程序静默失败,只需要将except代码块的内容改为一个pass语句。
下面的例子中,我们尝试统计alice.txt、siddhartha.txt、moby_dick.txt、little_women.txt四个文档分别包含了多少个单词,同时要求Python在未找到文件时静默失败。事先我已经将moby_dick.txt从文件夹中删除。
def count_words(filename):
'''计算一个文件大致包含多少个单词'''
try:
with open(filename,encoding='utf-8') as f: #当系统默认编码与读取文件的编码不一致时,需要指定encoding参数。
contents = f.read()
except FileNotFoundError:
pass
else:
words = contents.split() #split()方法以空格为分隔将字符串拆分成多个部分
num_words = len(words)
print(f"{filename}单词数约为{num_words}")
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)
alice.txt单词数约为29465
siddhartha.txt单词数约为42172
little_women.txt单词数约为189079
注:
①上例中调用open()函数时指定了两个参数,当系统默认编码与读取文件的编码不一致时,需要指定encoding参数。
②字符串的split()方法以空格为分隔将字符串拆分成多个部分。
事实上,当Python出现FileNotFoundError出现时,except代码块中的代码仍然被执行了,只不过pass语句相当于什么都不发生。此外,pass语句还可以充当占位符,提示此处什么都没有做,并且方便在以后可以对此处的功能进行补充。
关于异常的一些总结:一般来说,经过详尽测试的代码不容易出现内部错误(如语法错误或逻辑错误),但只要程序依赖于外部因素(如用户输入、存在指定的文件、有网络链接),就可能出现异常。凭借经验可判断该在哪里包含异常处理块,以及出现错误时该向用户提供多少相关的信息。
10.3 存储数据
许多程序都需要把用户提供的信息存储在列表和字典等数据结构中,关闭程序时几乎总是要保存他们提供的信息。
10.3.1 基础知识简介
1、Python对象
Python对象包括所有Python基本数据类型,列表,元组,字典,自定义类等。但是不包括Python的字符串类型,把字符串或者文件流中的字符串转为字符串会报错。(待考证)
2、序列化与反序列化
①序列化:把Python对象转换为字节序列的过程。
②反序列化:把字节序列恢复为Python对象的过程。
3、对象序列化的主要用途:
①持久化对象:把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
②网络传输对象:在网络上传送对象的字节序列。
4、JSON简介
JSON(JavaScript Object Notation, JS对象简谱)是一种轻量级的数据交换格式。最初JSON是为了JavaScript开发,它能够有效地提升网络传输效率,随后成为了被众多语言采用的常见格式。
5、Python中的json模块
Python中的json模块提供了一种很简单的方式来编码和解码JSON数据,json模块中主要包含四个方法:
①json.dumps()
②json.loads()
③json.dump()
④json.load()
①②是针对JSON编码的字节序列(本质是JSON字符串)使用的(补充内容)。
③④是针对.json文件而使用的(也是本章应该涉及的内容)。
①③是实现序列化的。
②④是实现反序列化的。
10.3.2 json.dumps()和json.loads()
json.dumps()用于将Python对象编码成JSON字符串(易传输)。它可以接收一个参数,即需要转换的Python对象。
json.loads()用于将已编码的JSON字符串解码为Python对象。它也可以接收一个参数,即需要转换的JSON字符串。
import json
data = [2, 3, 5, 7, 9]
json_data = json.dumps(data)
print(json_data)
print(type(json_data))
data2 = json.loads(json_data)
print(data2)
print(type(data2))
[2, 3, 5, 7, 9]
<class 'str'>
[2, 3, 5, 7, 9]
<class 'list'>
10.3.3 json.dump()和json.load()
json.dump()用于将Python对象编码成JSON字符串并储存到.json文件中(易存储)。它可以接收两个参数,需要存储的Python对象和用于存储数据的文件对象。
json.load()用于将.json文件解码为Python对象。它可以接收一个参数,即需要解码的.json文件。
import json
data = [2, 3, 5, 7, 9]
filename = 'data.json'
with open(filename,'w') as f:
json.dump(data,f)
可以看到程序所在文件夹中已经有了相关的文件。
import json
data = [2, 3, 5, 7, 9]
filename = 'data.json'
with open(filename) as f:
data2 = json.load(f)
print(data2)
print(type(data2))
[2, 3, 5, 7, 9]
<class 'list'>
注:简单记忆为带"s"是针对JSON字符串(str)使用的。
注:本节书上还有一个保存用户输入的例子,难度不大,从略;最后提了一下函数重构,我认为不属于此处的内容,从略。