到目前为止,我们对错误和异常讲的并不多,但至少肯定有两种大家见过了:syntax errors和exceptions。
8.1 Syntax Errors
语法错误也可以叫做解析错误,实在是让人非常头疼的问题:
解析程序返回了错误的行数,并且用箭头指出了最早发现错误的位置。当然后面也许还有错误,这个箭头是告诉我们错误至少在箭头前已经发生了,在这个例子中错误发生在print()函数之前,在这之前缺少一个冒号。最先返回的是错误的文件名,这可以方便我们知道是哪个脚本里的错误,要不自己找起来就太痛苦了。
8.2 Exceptions
即使一个声明或者表达式在语法上是正确的,但是在执行的时候可能也会有错误。这个错误就是异常,实在表达式执行的过程中自己调用的,他并不是无条件完全致命的错误,接下来我们很快会学到在
python中处理异常。大部分异常程序都不会处理,总之先看看例子:
错误信息的最后一行会提示发生了些什么,异常有很多种类型,消息中也会显示异常的类型,在例子中我们看到了3中异常:
ZeroDivisionError、
NameError、
TypeError。一般来说还是顾名思义都能清楚的明白发生了些啥。上面看到的这些异常类型都是内建的异常,这些内建的异常都会把异常类型打印出来,但是用户自建的可能会有所区别(当然自建的异常也是非常重要的)。省下的部分会提示更多的异常细节和引起异常的原因,错误信息前面的部分使用
trackback(这是一个日志工具很有用的)显示错误异常发生的位置。在标准库的
bltin-exception中有对内建异常的详细介绍。
8.3 Handling Exceptions
我们是可以通过异常的调用解决实际问题的,下面我们看个例子,检测用户的输入,直到一个合法数据被传入:
这个水印的效果真心不太好,以后就不用水印了,try协议是这么工作的:
首先执行try的语句,就是
try和
except之间这部分。
如果没有异常的话,
except部分就会被跳过,try的语句会被执行完。
如果在执行
try语句部分时发生了异常,那么try语句省下的部分都不会被执行,而是直接跳转到
except部分。如果发生的异常能和关键字匹配,那么就执行对应的操
作。
如果没能在
except中找到对应的异常关键字,他会到上一级中寻找,如果还是找不到的话执行就会终止并提示
unhandled exception。
一个
try协议可以拥有多个异常处理语句,但是最多只会执行一个,因为不能同时出现两个异常。但是一个
except语句可以用元组的包含多个异常,就是说自己个异常的处理方式是一样的:
... except (RuntimeError, TypeError, NameError):
... pass
... pass
在选择异常处理时,遵循的是基类优先,就是说子类可以对应上基类的异常处理。这么说好乱啊,我们还是直接看例子吧:
在这个例子中,会打出
B,B,B因为
C和
D都会直接对应上基类B的异常处理,想要打印出
B,C,D的话可以将
except反序的写,就是先写
D的处理,再写
C的处理,最后写
B的处理。最后一个
except语句其实是可以省略异常的关键字的,这是他就成了通配符,但是这种用法需要十二分的小心,因为她可能会把真正的错误隐藏掉。当然他也是可以打印错误信息的,还可以重新引起其他异常:
try...except协议还有另外一个
else选项,他必须写在
except之后,如果
try没有引起异常那么就执行
else内代码:
这样执行代码的好处是可以避免发生了其他的异常影响语句的执行。
当一个异常发生的时候,它可能还有几个伴随这个异常的变量。这些变量的类型和表现都是取决于异常的类型的,
except可以将变量指定给异常实例。这些变量会被保存在异常实例的
instance.
args中。异常实例定义了
__str__()所以这些变量都可以很方便的被打印出来,我们可以在
try语句中直接将变量指定给异常实例:
异常处理并不仅仅局限于
try语句的代码中,在其中调用其他部分如果引起对应的异常也会转到
except中:
8.4 Raising Exception
raise协议允许我们强行指定一个异常发生来看看它什么效果:
注意
raise只能引起它指定的那一个异常,不能同时引起多个。且引起的要么是异常实例,要么是异常类。如果传入一个类,他会直接调用构造器构造一个没有任何参数的实例,如果你想要引起一个异常但是还不打算解决它,那么可以在
except中再次
raise这个异常:
8.5 User-defined Exceptions
我们可以通过创建新的异常类来定义我们想要的异常,但是我们定义的异常类也应该继承自
exception基类。异常类其实可以做到其他类能做到的任何事,他在本质上与其他类并没有什么区别,但通常我们不并不会让他太过于复杂。一般我们只提供几个属性保证发生的错误能被处理即可:
我们先定义一个基类,然后用子类解决具体的问题。在命名规则上我们一般保证异常类都是以Error来结尾的。许多标准模块都会定义异常类来解决自己模块定义方法中出现的错误,我们会在class章节中看到更多的内容。
8.6 Defining Clean-up Actions
try协议还有另一个可选项叫做
finally,顾名思义他是在最后执行的,而且也是必须执行的~无论是否引起的异常:
在结束try协议之前
finally部分是一定会被执行的,无论是否发生了异常。如果在try语句中发生了异常,但是
except中没有对应的关键字,那么这个异常将会被暂时忽略,在执行完
finally的代码后在提起这个异常,我们看一个稍微复杂点的例子:
可以清楚的看到,
finally部分的代码肯定是会执行的。
TypeError的异常不在
except中,所以他是在
finally代码执行完了之后才被提起的。在实际编码工作中,
finally部分主要是用来释放外部资源的,无论它是否成功的被执行了。
8.7 Predefined Clean-up Actions
有些对象自身设置了清除动作,在资源不再被需要时自动清除,无论对他的使用是否成功。先看一个我们意见就学习过得打印文件的例子:
这段代码在执行之后,
workfile并没用被关闭,他会一直保存在内存器(记忆体)中,在一个简单的脚本中这没有什么问题,但是在一个大型的应用中这可能就会变得严重。使用
with结构可以很好的解决这个问题:
这段代码执行结束后,即使发生了异常,文件f也一定是被关闭的。包含有这样功能的对象都会在他们的说明中明确的指出。