第016课:⽂件读写和异常处理

实际开发中常常会遇到对数据进⾏持久化的场景,所谓持久化是指将数据从⽆法⻓久保存数据的存储介质(通常是内存)转移到可以⻓久保存数据的存储介质(通常是硬盘)中。实现数据持久化最直接简单的⽅式就是通过⽂件系统 将数据保存到 ⽂件 中。
计算机的 ⽂件系统 是⼀种存储和组织计算机数据的⽅法,它使得对数据的访问和查找变得容易,⽂件系统使⽤⽂件 树形⽬录 的抽象逻辑概念代替了硬盘、光盘、闪存等物理设备的数据块概念,⽤户使⽤⽂件系统来保存数据时,不必关⼼数据实际保存在硬盘的哪个数据块上,只需要记住这个⽂件的路径和⽂件名。在写⼊新数据之前,⽤户不必关⼼硬盘上的哪个数据块没有被使⽤,硬盘上的存储空间管理(分配和释放)功能由⽂件系统⾃动完成,⽤户只需要记住数据被写⼊到了哪个⽂件中。
打开和关闭⽂件
有了⽂件系统,我们可以⾮常⽅便的通过⽂件来读写数据;在 Python 中要实现⽂件操作是⾮常简单的。我们可以使⽤Python 内置的 open 函数来打开⽂件,在使⽤ open 函数时,我们可以通过函数的参数指定 ⽂件名 操作模式 字符编码 等信息,接下来就可以对⽂件进⾏读写操作了。这⾥所说的操作模式是指要打开什么样的⽂件(字符⽂件或⼆进制⽂件)以及做什么样的操作(读、写或追加),具体如下表所示。
操作模式
具体含义
'r'
读取 (默认)
'w'
写⼊(会先截断之前的内容)
'x'
写⼊,如果⽂件已经存在会产⽣异常
'a'
追加,将内容写⼊到已有⽂件的末尾
'b'
⼆进制模式
't'
⽂本模式(默认)
'+'
更新(既可以读⼜可以写)
下图展示了如何根据程序的需要来设置 open 函数的操作模式。

 

在使⽤ open 函数时,如果打开的⽂件是字符⽂件(⽂本⽂件),可以通过 encoding 参数来指定读写⽂件使⽤的字符编码。如果对字符编码和字符集这些概念不了解,可以看看 《字符集和字符编码》 ⼀⽂, 此处不再进⾏赘述。如果没有指定该参数,则使⽤系统默认的编码作为读写⽂件的编码。当前系统默认的编码可以通过下⾯的代码获得。
import sys
print(sys.getdefaultencoding())
使⽤ open 函数打开⽂件成功后会返回⼀个⽂件对象,通过这个对象,我们就可以实现对⽂件的读写操作;如果打开⽂件失败, open 函数会引发异常,稍后会对此加以说明。如果要关闭打开的⽂件,可以使⽤⽂件对象的 close ⽅法,这样可以在结束⽂件操作时释放掉这个⽂件。
读写⽂本⽂件
open 函数打开⽂本⽂件时,需要指定⽂件名并将⽂件的操作模式设置为 'r' ,如果不指定,默认值也是 'r' ;如果需要指定字符编码,可以传⼊ encoding 参数,如果不指定,默认值是 None ,那么在读取⽂件时使⽤的是操作系统默认的编码。需要提醒⼤家,如果不能保证保存⽂件时使⽤的编码⽅式与 encoding 参数指定的编码⽅式是⼀致的,那么就可能因⽆法解码字符⽽导致读取⽂件失败。
下⾯的例⼦演示了如何读取⼀个纯⽂本⽂件(⼀般指只有字符原⽣编码构成的⽂件,与富⽂本相⽐,纯⽂本不包含字符样式的控制元素,能够被最简单的⽂本编辑器直接读取)。
file = open('致橡树.txt', 'r', encoding='utf-8')
print(file.read())
file.close()
说明 《致橡树》 是舒婷⽼师在 1977 3 ⽉创建的爱情诗,也是我最喜欢的现代诗之⼀。
除了使⽤⽂件对象的 read ⽅法读取⽂件之外,还可以使⽤ for-in 循环逐⾏读取或者⽤ readlines ⽅法将⽂件按⾏读取到⼀个列表容器中,代码如下所示。
import time
file = open('致橡树.txt', 'r', encoding='utf-8')
for line in file:
 print(line, end='')
 time.sleep(0.5)
file.close()
file = open('致橡树.txt', 'r', encoding='utf-8')
lines = file.readlines()
for line in lines:
 print(line, end='')
 time.sleep(0.5)
file.close()
如果要向⽂件中写⼊内容,可以在打开⽂件时使⽤ w 或者 a 作为操作模式,前者会截断之前的⽂本内容写⼊新的内容,后者是在原来内容的尾部追加新的内容。
file = open('致橡树.txt', 'a', encoding='utf-8')
file.write('\n标题:《致橡树》')
file.write('\n作者:舒婷')
file.write('\n时间:1977年3⽉')
file.close()
也可以使⽤下⾯的代码来完成相同的操作。
lines = ['标题:《致橡树》', '作者:舒婷', '时间:1977年3⽉']
file = open('致橡树.txt', 'a', encoding='utf-8')
for line in lines:
 file.write(f'\n{line}')
file.close()
异常处理机制
请注意上⾯的代码,如果 open 函数指定的⽂件并不存在或者⽆法打开,那么将引发异常状况导致程序崩溃。为了让代码具有健壮性和容错性,我们可以使⽤ Python 的异常机制对可能在运⾏时发⽣状况的 代码进⾏适当的处理 Python 中和异常相关的关键字有五个,分别是 try except else finally和 raise ,我们先看看下⾯的代码,再来为⼤家介绍这些关键字的⽤法。
file = None
try:
 file = open('致橡树.txt', 'r', encoding='utf-8')
 print(file.read())
except FileNotFoundError:
 print('⽆法打开指定的⽂件!')
except LookupError:
 print('指定了未知的编码!')
except UnicodeDecodeError:
 print('读取⽂件时解码错误!')
finally:
 if file:
 file.close()
Python 中,我们可以将运⾏时会出现状况的代码放在 try 代码块中,在 try 后⾯可以跟上⼀个或多 个 except 块来捕获异常并进⾏相应的处理。例如,在上⾯的代码中,⽂件找不到会引发FileNotFoundError ,指定了未知的编码会引发 LookupError ,⽽如果读取⽂件时⽆法按指定编码⽅式解码⽂件会引发 UnicodeDecodeError ,所以我们在 try 后⾯跟上了三个 except 分别处理这三种不同的异常状况。在 except 后⾯,我们还可以加上 else 代码块,这是 try 中的代码没有出现异常时会执⾏的代码,⽽且 else 中的代码不会再进⾏异常捕获,也就是说如果遇到异常状况,程序会因异常⽽终⽌并报告异常信息。最后我们使⽤ finally 代码块来关闭打开的⽂件,释放掉程序中获取的外部资源。由于finally 块的代码不论程序正常还是异常都会执⾏,甚⾄是调⽤了 sys 模块的 exit 函数终⽌ Python 程序, finally 块中的代码仍然会被执⾏(因为 exit 函数的本质是引发了 SystemExit 异常),因此我们把 finally 代码块称为 总是执⾏代码块 ,它最适合⽤来做释放外部资源的操作。
Python 中内置了⼤量的异常类型,除了上⾯代码中⽤到的异常类型以及之前的课程中遇到过的异常类型 外,还有许多的异常类型,其继承结构如下所示。
BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
 +-- StopIteration
 +-- StopAsyncIteration
 +-- ArithmeticError
 | +-- FloatingPointError
 | +-- OverflowError
 | +-- ZeroDivisionError
 +-- AssertionError
 +-- AttributeError
 +-- BufferError
 +-- EOFError
 +-- ImportError
 | +-- ModuleNotFoundError
 +-- LookupError
 | +-- IndexError
| +-- KeyError
 +-- MemoryError
 +-- NameError
 | +-- UnboundLocalError
 +-- OSError
 | +-- BlockingIOError
 | +-- ChildProcessError
 | +-- ConnectionError
 | | +-- BrokenPipeError
 | | +-- ConnectionAbortedError
 | | +-- ConnectionRefusedError
 | | +-- ConnectionResetError
 | +-- FileExistsError
 | +-- FileNotFoundError
 | +-- InterruptedError
 | +-- IsADirectoryError
 | +-- NotADirectoryError
 | +-- PermissionError
 | +-- ProcessLookupError
 | +-- TimeoutError
 +-- ReferenceError
 +-- RuntimeError
 | +-- NotImplementedError
 | +-- RecursionError
 +-- SyntaxError
 | +-- IndentationError
 | +-- TabError
 +-- SystemError
 +-- TypeError
 +-- ValueError
 | +-- UnicodeError
 | +-- UnicodeDecodeError
 | +-- UnicodeEncodeError
 | +-- UnicodeTranslateError
 +-- Warning
 +-- DeprecationWarning
 +-- PendingDeprecationWarning
 +-- RuntimeWarning
 +-- SyntaxWarning
 +-- UserWarning
 +-- FutureWarning
 +-- ImportWarning
 +-- UnicodeWarning
 +-- BytesWarning
 +-- ResourceWarning
从上⾯的继承结构可以看出, Python 中所有的异常都是 BaseException 的⼦类型,它有四个直接的⼦类,分别是: SystemExit KeyboardInterrupt GeneratorExit Exception 。其中, SystemExit 表示解释器请求退出, KeyboardInterrupt 是⽤户中断程序执⾏(按下 Ctrl+c ), GeneratorExit 表示⽣成器发⽣异常通知退出。值得⼀提的是 Exception 类,它是常规异常类型的⽗类型,很多的异常都是直接或间接的继承⾃ Exception 类。如果 Python 内置的异常类型不能满⾜应⽤程序的需要,我们可以⾃定义异常类型,⽽⾃定义的异常类型也应该直接或间接继承自Exception 类,当然还可以根据需要重写或添加⽅法。
Python 中,可以使⽤ raise 关键字来引发异常(抛出异常对象),⽽调⽤者可以通过try...except... 结构来捕获并处理异常。例如在函数中,当函数的执⾏条件不满⾜时,可以使⽤抛出
异常的⽅式来告知调⽤者问题的所在,⽽调⽤者可以通过捕获处理异常来使得代码从异常中恢复,定义异常和抛出异常的代码如下所示。
class InputError(ValueError):
 """⾃定义异常类型"""
 pass
def fac(num):
 """求阶乘"""
 if type(num) != int or num < 0:
 raise InputError('只能计算⾮负整数的阶乘!!!')
 if num in (0, 1):
 return 1
 return num * fac(num - 1)
调⽤求阶乘的函数 fac ,通过 try...except... 结构捕获输⼊错误的异常并打印异常对象(显示异常信
息),如果输⼊正确就计算阶乘并结束程序。
flag = True
while flag:
 num = int(input('n = '))
 try:
 print(f'{num}! = {fac(num)}')
 flag = False
 except InputError as err:
 print(err)
上下⽂语法
对于 open 函数返回的⽂件对象,还可以使⽤ with 上下⽂语法在⽂件操作完成后⾃动执⾏⽂件对象的close ⽅法,这样可以让代码变得更加简单,因为不需要再写 finally 代码块来执⾏关闭⽂件释放资源的操作。需要提醒⼤家的是,并不是所有的对象都可以放在 with 上下⽂语法中,只有符合 上下⽂管理 器协议 (有 __enter__ __exit__ 魔术⽅法)的对象才能使⽤这种语法, Python 标准库中的contextlib 模块也提供了对 with 上下⽂语法的⽀持,后⾯再为⼤家进⾏讲解。
try:
 with open('致橡树.txt', 'r', encoding='utf-8') as file:
 print(file.read())
except FileNotFoundError:
 print('⽆法打开指定的⽂件!')
except LookupError:
 print('指定了未知的编码!')
except UnicodeDecodeError:
 print('读取⽂件时解码错误!')
读写⼆进制⽂件
读写⼆进制⽂件跟读写⽂本⽂件的操作类似,但是需要注意,在使⽤ open 函数打开⽂件时,如果要进⾏读操作,操作模式是 'rb' ,如果要进⾏写操作,操作模式是 'wb' 。还有⼀点,读写⽂本⽂件
时, read ⽅法的返回值以及 write ⽅法的参数是 str 对象(字符串),⽽读写⼆进制⽂件时, read ⽅法的返回值以及 write ⽅法的参数是 bytes-like 对象(字节串)。下⾯的代码实现了将当前路径下名为 guido.jpg 的图⽚⽂件复制到 吉多 .jpg ⽂件中的操作。
try:
 with open('guido.jpg', 'rb') as file1:
 data = file1.read()
 with open('吉多.jpg', 'wb') as file2:
 file2.write(data)
except FileNotFoundError:
 print('指定的⽂件⽆法打开.')
except IOError:
 print('读写⽂件时出现错误.')
print('程序执⾏结束.')
如果要复制的图⽚⽂件很⼤,⼀次将⽂件内容直接读⼊内存中可能会造成⾮常⼤的内存开销,为了减少对内存的占⽤,可以为 read ⽅法传⼊ size 参数来指定每次读取的字节数,通过循环读取和写⼊的⽅式来完成上⾯的操作,代码如下所示。
try:
 with open('guido.jpg', 'rb') as file1, \
 open('吉多.jpg', 'wb') as file2:
 data = file1.read(512)
 while data:
 file2.write(data)
 data = file1.read()
except FileNotFoundError:
 print('指定的⽂件⽆法打开.')
except IOError:
 print('读写⽂件时出现错误.')
print('程序执⾏结束.')
简单的总结
通过读写⽂件的操作,我们可以实现数据持久化。在 Python 中可以通过 open 函数来获得⽂件对象,可以通过⽂件对象的 read write ⽅法实现⽂件读写操作。程序在运⾏时可能遭遇⽆法预料的异常状况,可以使⽤Python 的异常机制来处理这些状况。 Python 的异常机制主要包括try 、 except else finally raise 这五个核⼼关键字。 try 后⾯的 except 语句不是必须的, finally 语句也不是必须的,但是⼆者必须要有⼀个; except 语句可以有⼀个或多个,多个 except 会按照书写的顺序依次匹配指定的异常,如果异常已经处理就不会再进⼊后续的 except 语句; except 语句中还可以通过元组同时指定多个异常类型进⾏捕获; except 语句后⾯如果不指定异常类型,则默认捕获所有异常;捕获异常后可以使⽤ raise 要再次抛出,但是不建议捕获并抛出同⼀个异常;不建议在不清楚逻辑的情况下捕获所有异常,这可能会掩盖程序中严重的问题。最后强调⼀点,不
要使⽤异常机制来处理正常业务逻辑或控制程序流程,简单的说就是不要滥⽤异常机制。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值