在现实世界中,无论我们多么小心,编写的代码总是会因为一些外部的原因发生错误。例如,被粗心的开发者传递了错误的参数、要打开的文件被误删除了、操作系统发生了未知错误等等。哪怕这些问题都不存在,用户还可以在你的程序过程中通过Ctrl + C
强制结束呢。所以,就像我们要一丝不苟的编写逻辑正确的代码一样,在错误处理上,我们同样马虎不得。
不过好在,相比那些有很多种表达错误方式的语言,Python只有一种表达错误的方式,就是Exception
,自然,处理错误的方式,也就只有一种了。
有哪些常用的异常
在开始着手处理异常之前,基于我们已经用到过的内容,来看下Python提供了哪些常用的异常类:
-
Exception
:这是所有异常的基类; -
AttributeError
:访问了不存在的类属性,例如:"Hello world".size
; -
IndexError
:访问list
或者tuple
越界,例如:(1, 2)[2]
; -
KeyError
:访问了dict
中不存在的key,例如:{'a':1}['b']
; -
NameError
:访问了一个不存在的名称,例如:notExist()
; -
SyntaxError
:很简单,表示语法错误,例如:notExist(
; -
TypeError
:调用函数的时候传递了类型错误的参数,例如:"Hello".index(1)
; -
ValueError
:调用函数的时候,传递的参数类型正确,但是值不正确,例如:int('s')
; -
ZeroDivisionError
:除数为0,例如:3/0
;
当然,还有一些和操作系统相关的异常,我们也可以自定义异常,这些就都等我们用到的时候再说了。
如何处理异常
接下来,我们来看如何处理这些常见的异常,如果你之前用过任何一种支持面向对象编程的语言,就可以直接把经验移植到Python的错误处理中。我们同样使用try
block包围可能会发生错误的代码,但在Python里,我们要使用except
捕获希望处理的异常:
try:
3/0
except ZeroDivisionError:
print('Divide by zero')
这里要注意,try
和except
语句的末尾,也要使用冒号表示结束。简单来说就是,在Python里,所有需要引入代码块的地方,都需要使用一个冒号表示。
执行一下,就能在控制台看到打印出来的Divide by zero了。如果我们的代码里有多处可能发生异常的地方,就可以把多个except
语句并列起来:
try:
# 3/0
fn()
except ZeroDivisionError:
print('Divide by zero')
except NameError:
print('Invalid name')
或者,你也可以写一个接管所有的异常的默认处理方法,像这样:
try:
# 3/0
# fn()
"Hello".index(1)
except ZeroDivisionError:
print('Divide by zero')
except NameError:
print('Invalid name')
except: # This is a too broad exception clause
print('Default handler')
实际上,我个人并不是很喜欢最后这种像“接盘侠”一样的错误处理方式,当项目中到处充斥着这样的处理方法之后,会隐藏掉很多潜在的异常错误。甚至,有些IDE都会提示你Too broad exception clause。
而在下一节中我们会看到,Python中的一个设计准则,叫做Errors should never pass silently,但是显然,这种“万用except
”对代码的使用者而言,破坏了这个准则。
理解finally和else
以上,就是Python中处理错误的基本用法。除了每种错误特定的处理代码之外,我们有时还需要在错误发生之后执行一些公共的代码,例如,关闭文件句柄、数据库连接、释放线程锁等等。把这些代码重复写在每个except
里显然是不能接受的。为此,Python提供了finally
关键字,我们可以这样:
try:
# 3/0
fn()
except ZeroDivisionError:
print('Divide by zero')
except NameError:
print('Invalid name')
finally:
print('Clean up actions.')
这次,我们就可以在控制台看到两个消息:Invalid name和Clean up actions.。因此,只要记住,把所有事后清理类的代码,都写在finally
代码块里,就好了。
实际上,无论错误发生与否,
finally
block中的代码都会被执行。
但事情至此还没有结束,和for
循环类似,Python为try
语句也提供了一个else
,表示“当try
代码块中没有任何错误发生的时候,执行的代码”:
try:
3/0
except ZeroDivisionError:
print('Divide by zero')
else:
print("No errors.")
finally:
print('Clean up actions.')
在上面这个例子里,由于会发生ZeroDivisionError
,因此执行的会是对应的except
和finally
,else
分支并不会执行。但如果我们改成这样:
try:
3/1
except ZeroDivisionError:
print('Divide by zero')
else:
print("No errors.")
finally:
print('Clean up actions.')
就可以在控制台看到No errors.和Clean up actions.这两个消息了。但是,你可能会想,为什么需要else
呢?如果try
代码块没有异常发生,那把接下来的语句直接写在finally
后面不就好了么?
实际上,我也的确没发现特别的合适的这种else
用法,因此,只要知道它就好了。当你看到别人的代码中有这样的用法时,明白它在做什么也就是了。
网友评论