此系列文章的创作初衷是作为读书过程中的笔记,而非教程类文章。
第10章 文件和异常
10.1 从文件中读取数据
读取整个文件
- 下面是一个读取文件的示例:
filePath = "test.txt"
with open(filePath) as file:
contents = file.read()
print(contents)
- 通过open函数打开文件。open函数接收文件的路径(可以使用绝对路径或相对路径),并返回一个文件对象。此处open()返回一个文本文件。
- 此示例使用了with关键字来打开文件,with语句可以在文件访问接收后自动将文件关闭,类似于Java中的try-with-resources语句。也可以使用opne()和close()完成打开和关闭文件的任务,但不推荐这么做——一是因为可能忘记调用close();二是因为如果程序出现bug导致close()未调用,则文件将不会关闭。
- read()函数会一次性读取文件的全部内容。如果文件末尾有换行符,则read函数会将换行符也一并读入,在输出时将多显示一个换行(注意print()函数也会换行)。若要去除多余的换行符,可以使用rstrip()方法。
- Python以字符串的形式读取文件内容。如果希望知道文件中是否包含特定的内容,可以使用in关键字:
filePath = "test.txt"
keyword = "some word"
with open(filePath) as file:
contents = file.read()
if keyword in contents:
# do something
pass # 为保证语法正确使用的占位关键字,不会做任何事情
- 要将文件中的某部分内容全部替换为另一部分内容(例如,要将一个单词全部替换为另一个单词),可以使用replace()方法:
filePath = "test.txt"
src = "wordA"
target = "wordB"
with open(filePath) as file:
contents = file.read()
contents = contents.replace(src, target) # replace()方法不会修改原字符串,应将方法返回的结果赋给变量来实现修改
print(contents)
文件路径
- 在Windows中,使用反斜杠分隔文件目录。由于反斜杠同时是转义字符,应用双反斜杠表示反斜杠;或者,也可以在字符串开头加上一个“r”表示使用原始字符串,这样python就不会试图将反斜杠解释为转义字符。
open(r"C:\Users\Administrator\Documents\test.txt")
- 推荐将程序需要使用的文件置于程序文件所在的目录或子目录下,并使用相对路径。这样即使程序迁移,也能保证路径正确。
逐行读取
- 对文本文件对象使用for语句,可以逐行读取文件内容:
filePath = "test.txt"
with open(filePath) as file:
for line in file:
print(line.strip())
由于文件内容的每一行末尾都存在换行符,此处用strip()方法将之去除。
10.2 写入文件
- 下面是一段打开并写入文件的样例代码:
with open("test.txt", "w+") as file:
file.write("I like Python.\n")
file.write("But I like Java more.\n")
file.write(str(1))
- open()的第二个实参表示打开文件的方式,常用的方式有以下几种:
- “r”:只读模式,默认的模式。文件不存在时抛出FileNotFoundError。
- “w”:写入模式(只写)。当文件不存在时会新建文件,当文件已存在时将清空原文件的内容。
- “a”:追加模式(只写)。当文件不存在时会新建文件;当文件已存在时,将指针移至文件末尾,从此处开始追加内容。
- “r+”:读写模式。文件存在时,将指针置于文件开头,允许读写;文件不存在时抛出FileNotFoundError。
- “w+”:读写模式。当文件不存在时会新建文件,当文件已存在时将清空原文件的内容。
- “a+”:读写模式。当文件不存在时会新建文件;当文件已存在时,将指针移至文件末尾,允许读写。
- 此外,还有"rb","rb+"等模式,这些模式用于以二进制方式进行读写,不在本教材的讨论范围内,此处略去。
- write()函数可用于向文件写入字符串。write()不会自动追加换行符,需要注意手动追加。此外,write()只用于写入字符串,如果要写入数字,需要用str()将其转为字符串。
10.3 异常
- Python使用称为异常的对象来管理程序执行期间发生的错误。如果编写了处理异常的代码,程序将在处理异常后继续运行;否则,程序将终止,并显示一个traceback,其中包含了有关异常信息的报告。
try-except-else
- Python使用try-except结构处理异常,在希望遇到异常时进行更多操作时,还可能用到try-except-finally或try-except-else,甚至try-except-else-finally结构。使用try-except处理异常,有助于让程序更健壮,同时也有助于提升用户体验:用户能明确地知道程序出现了何种一场,而无需面对令人迷惑的traceback。使用try-except的额外好处在于,它可以避免恶意用户借助traceback获悉你的文件名或是出错的部分代码,这将有助于避免代码遭受攻击。
- 下面是一段try-except-else的样例代码:
def main(a: int, b: int) -> None:
try:
ans = a / b
except ZeroDivisionError:
print("不能将0作为除数。")
except IndexError:
pass
else:
print(ans)
print("After Try")
- Python首先执行try块中的代码。只有可能引起异常的代码才应放在try块中。在本例中,ans = a / b是可能引起异常的代码,当b为0时,将抛出ZeroDivisionError。
- except告诉Python在发生指定的异常时应如何处理。上述的ans = a / b语句抛出异常后,Python将在except列表中逐个寻找异常类型是否匹配,如果找到匹配的语句块就执行语句块,然后执行try-except-else结构后的print()语句;如果未能找到匹配的except块,将导致程序终止。无论是否找到匹配的except语句,都将跳过else语句块。
- 只有在try中的语句未导致异常时,else语句块中的语句才会执行。
- 有时候,希望程序在捕获异常时什么也不做,这时可以在except块中写pass。pass是一个占位语句,当Python遇到pass时,什么也不会做。同时,pass还避免了块中没有语句导致的语法错误。上面try块中的代码不会抛出IndexError,只是为了展示pass的作用而写的。
- finally语句块置于try,except和else之后,用于放置需要确保执行的语句。即使在try块中抛出的异常没有被捕获,或者try/except/else语句中使用了return,也会确保进入finally语句块。finally不在教材的讨论范围内,不再详细讨论。
with-as语句与异常
- with-as语句本质上启动了所谓“上下文管理器”协议,该协议本质上是一个try-finally结构。例如下面的代码:
with open("test.txt") as file:
pass # 占位语句,什么都不会做
可以理解为下面的代码(但并不等效):
try:
file = open("test.txt")
pass
finally:
file.close()
因此,即使with-as块中出现了异常,或者使用了return,仍然能够确保文件被关闭。
split(delimiter: str) -> list
- split()函数以delimiter为界,将字符串分割为多个子串,并置于一个列表中。当不传入任何参数时,分隔符为空格。
- 注意:split(delimiter: str) -> list并不是split()函数的签名,只是为了方便说明才这样写的。
10.4 存储数据
- 可以使用json存储数据。存储和读取数据依赖json.dump()和json.load()函数。
- json.dump()的简化版原型如下:
dump(obj: any, fp: IO[str]) -> None
obj是需要向文件写入的对象,可以是整型、浮点、字符串、列表、字典等,fp是用于写入的文件对象。对于上面提及的五种类型,json存储的形式与print()输出的形式完全相同(除了单引号’在json中表示为双引号")。
- json.load()接受文件对象作为参数。函数读取json文件,并将内容作为对象返回。例如,一个json文件中存储了如下内容:
[1, 2, 3]
json.load()会将内容识别为列表并返回。