异常,什么是异常呢?
有没有见过在浏览网页出现 500 错误?!
图片来自百度,侵删!
如果没见过,那肯定在学习 Python 过程中见过如下情景:
不管是浏览网页时出现的 500 错误,还是在写代码过程中的 “报红”,这些都是代码在运行过程中出现了异常。
这些错误如果不处理会出现什么样的情况呢?当前运行的代码会中断。
print('start')
print(name) # 注意,这里的 name 没有引号
print('end')
上图中的 name 没有打引号,会被 Python 认为是一个变量,而如果这个变量没有定义,那么会出现下面的错误:
我们可以看到
start
成功的打印了出来,但是 end
却没有,这是因为 第二句print(name)
抛出了异常。
那么会有什么影响呢?假如,我们是在操作文件或数据库的过程中出现了异常:
# 伪代码,只是为了说明操作文件和数据库的过程
# 操作文件过程
f = open('nemo.txt') # 打开文件
f.read() # 读取文件内容
f.close() # 关闭文件
# 操作数据库过程
db = connect() # 连接数据库
db.options # 数据库操作过程
db.close() # 断开数连接
如果在这样的处理过程中,操作文件的过程或者操作数据库的过程中报错了,会导致后续的文件关闭和数据库连接无法正常断开。这就会造成内存驻留和数据库的连接被占用。
通常,为了保证关闭操作能够正常进行,我们都需要处理操作过程中可能出现的异常。当然还有很多其他的用处!
在 Python 中,处理异常的语句为try...except...finally(else)...
。
具体语法如下:
try:
可能出现异常的语句块
except 错误类型:
如果出现错误后,执行的代码
else:
没有出现错误,执行这里的代码
finally:
不管有没有出现错误,这里的代码都会执行
举例来说。假设我们现在想写一个函数,用来进行出发运算,但是除数不能为0,为0就会报一个异常ZeroDivisionError
,当用户不小心把被除数数为0,我们不能直接给用户一个异常扔脸上吧!所以我们至少要处理一下异常,并告诉用户你输错了。
def division(a, b):
'''a 为被除数,b 为除数'''
try:
return a/b
except ZeroDivisionError:
print('除数不能为 0 !')
>>> division(10,2)
5.0
>>> division(5,0)
除数不能为 0 !
但是用户除了可能会乱输 0 以外,还可能输一些命名其妙的符号。
>>> division(10,'$@]')
...异常堆栈信息省略
TypeError: unsupported operand type(s) for /: 'int' and 'str'
咦,不是做了异常处理么?咋还要报错呢?
这回错误不一样了,是
TypeError
。
回头看看我们写的函数,我们只处理了ZeroDivisionError
这种异常,而对于非数字类型的错误没有处理。那要处理怎么办呢?
针对上面报的TypeError
,再加一个 except 处理:
def division(a, b):
'''a 为除数,b 为被除数'''
try:
return a/b
except ZeroDivisionError:
print('被除数不能为 0 !')
except TypeError:
print('除法只支持数值类型!')
>>> division(10,'$@]')
除法只支持数值类型!
好了,这种异常也处理了。可能有人会问,有没有能一次性处理所有异常的方式呢?
当然有,就是使用所有异常类型(每种异常类型是 Python 中的一个异常类)的父类Exception
。
def division(a, b):
'''a 为被除数,b 为除数'''
try:
return a/b
except Exception:
print('你的输入有误!')
>>> division(10,'$@]')
你的输入有误!
>>> division(5,0)
你的输入有误!
我们会发现,虽然这种方式能处理所有异常,但是没有办法针对不同的情况给予用户合适的提示。所以根据情况选用!
接下来,我们说一下finally
和else
子句。
先说 else,我们改造一下除法函数,当输入正确的情况下,我们告诉用户除法的结果。
def division(a, b):
'''a 为被除数,b 为除数'''
try:
r = a/b
except ZeroDivisionError:
print('除数不能为 0 !')
except TypeError:
print('除法只支持数值类型!')
else:
print(f'{a} 除以 {b} 的结果是 {r}')
>>> division(10,'$@]')
除法只支持数值类型!
>>> division(10, 2)
10 除以 2 的结果是 5.0
在某些情况下,可以使用 try...except...else...
代替if...else...
来使用。
最后就是 finally 子句了,finally 是无论如何都会执行的。我们看下面的例子
a = input('请输入一个数:')
try:
a = int(a)
b = 100 / a
except ValueError:
print('请输入整数!')
print('end')
当某种异常没有被捕获到的时候,我们发现后面的 print('end')
并没有执行。如果我们想无论如何都执行 print('end')
,就可以使用 finally。
a = input('请输入一个数:')
try:
a = int(a)
b = 100 / a
except ValueError:
print('请输入整数!')
finally:
print('end')
请输入一个数:0
end
...异常堆栈省略
ZeroDivisionError: division by zero
我们可以看到 end 也会打印出来,异常也会抛出来。
那么对于文件操作就可以将 close() 关闭语句放在 finally 中,以保证无论什么情况下文件都能被正常关闭。
f = open('nemo.txt')
try:
f.read()
except Exception as e: # as e,把异常提示保存在 e 变量中
print(e) # 打印异常的提示,如 ''ZeroDivisionError: division by zero''
finally:
f.close() # 放在 finally 语句中,无论操作过程报什么错,都会正常关闭文件