在shell脚本中,经常使用if来判断程序的某个部分是否可能会出错,并在if的分支中作出对应的处理,从而让程序更具健壮性。if判断是异常处理的一种方式,全部语言都通用。对于特性完整的编程语言来讲,都有专门的异常处理机制,有些语言用起来可能会很复杂,要求一堆堆的,有些语言则很是简洁,用起来很是通畅。html
对于索引查找的操做,在索引越界搜索的时候会报错。例如:python
>>> s="long" >>> s[4] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range
所报的错误是IndexError。若是将索引查找放在一个函数里:shell
>>> def fetcher(obj,index): ... return obj[index]
那么调用函数的时候,若是里面的索引越界了,异常将汇报到函数调用者。编程
>>> fetcher(s,3) 'g' >>> fetcher(s,4) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in fetcher IndexError: string index out of range
可使用try/except来捕获异常。做为入门示例,下面是简单版的格式:json
try: statement1 ... statementN except <ERRORTYPE>: ...statementS...
例如,try中是要监视正确执行与否的语句,ERRORTYPE是要监视的错误类型。less
例如捕获上面的函数调用:编程语言
def fetcher(obj, index): return obj[index] s = "long" try: print(fetcher(s, 3) * 4) print(fetcher(s, 4) * 4) except IndexError: print("something wrong") print("after Exception, Continue")
输出结果:ide
gggg something wrong after Exception, Continue
由于上面的fetcher(s, 4)
会抛出异常,且正好匹配except监视的异常类型,因此输出something wrong
,异常被处理以后,程序继续执行,即try/except后面的print()。函数
finally是try以后必定会执行的语句段落。能够结合except一块儿使用。测试
try: statement1 ... statementN finally: ...statementF...
try: statement1 ... statementN except <ERRORTYPE>: ...statementS... finally: ...statementF...
不论try中的语句是否出现异常,不论except是否捕获到对应的异常,finally都会执行:
通常来讲,finally中都会用来作程序善后清理工做。
例如:
def fetcher(obj, index): return obj[index] s = "long" try: print(fetcher(s, 3) * 4) print(fetcher(s, 4) * 4) except IndexError: print("something wrong") finally: print("in finally") print("after Exception, Continue")
输出:
gggg something wrong in finally after Exception, Continue
若是把except那段代码删掉,获得的结果将是:
gggg in finally # 输出了finally的内容 Traceback (most recent call last): File "g:/pycode/list.py", line 8, in <module> print(fetcher(s, 4) * 4) File "g:/pycode/list.py", line 2, in fetcher return obj[index] IndexError: string index out of range
使用raise或assert能够主动生成异常状况。其中raise能够直接抛出某个异常,assert须要经过布尔值来判断,而后再抛出给定的错误。
例如,在函数里作个没什么用的的判断,用来演示raise:
def fetcher(obj, index): if index >= len(obj): raise IndexError return obj[index]
这和直接索引越界是以同样的。上面raise抛出的异常IndexError是一个内置异常,能够直接引用这些内置异常。稍后会演示如何自定义本身的异常。
抛出异常后,就能够按照前面介绍的try来处理异常。
assert是一种断言,在计算机语言中表示:若是断言条件为真就跳过,若是为假就抛出异常信息。它能够自定义异常信息。
例如:
def fetcher(obj, index): assert index < len(obj), "one exception" return obj[index]
不少时候会直接在assert中使用False、True的布尔值进行程序的调试。
assert True, "assert not hit" assert False, "assert hit"
python中的异常是经过类来定义的,并且全部的异常类都继承自Exception类,而Exception又继承自BaseException(这个类不能直接做为其它异常类的父类)。因此自定义异常的时候,也要继承Exception,固然,继承某个中间异常类也能够。
例如,定义索引越界的异常类,注意这个类中直接pass,但由于继承了Exception,它仍然会有异常信息。
class MyIndexError(Exception): pass
例如,判断字母是不是大写,若是是,就抛异常:
def fetcher(obj,index): if index >= len(obj): raise MyIndexError return obj[index]
测试一下:
s = "long" print(fetcher(s, 3) * 4) print(fetcher(s, 4) * 4)
结果:
gggg Traceback (most recent call last): File "g:/pycode/list.py", line 12, in <module> print(fetcher(s, 4) * 4) File "g:/pycode/list.py", line 6, in fetcher raise MyIndexError __main__.MyIndexError
须要注意,由于异常类都继承字Exception,except监视Exception异常的时候,也会匹配其它的异常。更标准地说,监视异常父类,也会捕获到这个类的子类异常。
看异常信息是最基本的能力。例如,下面的这段代码会报除0错误:
def a(x, y): return x/y def b(x): print(a(x, 0)) b(1)
执行时,报错信息以下:
Traceback (most recent call last): File "g:/pycode/list.py", line 7, in <module> b(1) File "g:/pycode/list.py", line 5, in b print(a(x, 0)) File "g:/pycode/list.py", line 2, in a return x/y ZeroDivisionError: division by zero
这个堆栈跟踪信息中已经明确说明了(most recent call last)
,说明最近产生异常的调用在最上面,也就是第7行。上面的整个过程是这样的:第7行出错,它是由于第5行的代码引发的,而第5行之因此错是第2行的源代码引发的。
因此,从最底部能够看到最终是由于什么而抛出异常,从最顶部能够看到是执行到哪一句出错。
try: <statements> except <name1>: # 捕获到名为name1的异常 <statements> except (name2, name3): # 捕获到name2或name3任一异常 <statements> except <name4> as <data>: # 捕获name4异常,并获取异常的示例 <statements> except: # 以上异常都不匹配时 <statements> else: # 没有产生异常时 <statements> finally: # 必定会执行的 <statements>
注意,当抛出的异常没法被匹配时,将归类于空的except:
,但这是很危险的行为,由于不少时候的异常是必然的,好比某些退出操做、内存不足、Ctrl+C等等,而这些都会被捕获。与之大体等价的是捕获异常类的"伪"祖先类Exception
,即except Exception:
,它和空异常匹配相似,但能解决很多不该该匹配的异常。但使用Exception依然是危险的,能不用尽可能不用。
若是一个异常既能被name1匹配,又能被name2匹配,则先匹配到的处理这个异常。
经过as关键字能够将except捕获到的异常对象赋值给data变量。用法稍后会解释,如今须要知道的是,在python 3.x中,变量data只在当前的except块范围内有效,出了范围就会被回收。若是想要保留异常对象,能够将data赋值给一个变量。例以下面的b在出了try范围都有效,可是a在这个except以后就无效了。
except Exception as a: print(a) b=a
经过else分句能够知道,这段try代码中没有出现任何异常。不然就不会执行到else分句。
raise用于手动触发一个异常。而每一种异常都是一个异常类,因此触发其实是触发一个异常类的实例对象。
raise <instance> # 直接触发一个异常类的对象 raise <class> # 构建此处所给类的一个异常对象并触发 raise # 触发最近触发的异常 raise <2> from <1> # 将<1>的异常附加在<2>上
其中第二种形式,raise会根据给定类不传递任何参数地自动构建一个异常对象,并触发这个异常对象。第三种直接触发最近触发的异常对象,这在传播异常的时候颇有用。
例如,下面两种方式其实是等价的,只不过第一种方式传递的是类,raise会隐式地自动建立这个异常类的实例对象。
raise IndexError raise IndexError()
能够为异常类构建实例时指定点参数信息,这些参数会保存到名为args的元组。例如:
try: raise IndexError("something wrong") except Exception as E: print(E.args)
输出:
('something wrong',)
不只如此,只要是异常类或异常对象,无论它们的存在形式如何,均可以放在raise中。例如:
err = IndexErro() raise err errs = [IndexError, TypeError] raise errs[0]
对于第三种raise形式,它主要用来传播异常,通常用在except代码段中。例如:
try: raise IndexError("aaaaa") except IndexError: print("something wrong") raise
由于异常被except捕获后,就表示这个异常已经处理过了,程序会跳转到finally或整个try块的尾部继续执行下去。可是若是不想让程序继续执行,而是仅仅只是想知道发生了这个异常,并作一番处理,而后继续向上触发异常。这就是异常传播。
由于实际触发的异常都是类的实例对象,因此它有属性。并且,能够经过在except中使用as来将对象赋值给变量:
try: 1/0 except Exception as a: print(a)
变量a在出了except的范围就失效,因此能够将它保留给一个不会失效的变量:
try: 1/0 except Exception as a: print(a) b=a print(b)
若是在一个except中触发了另外一个异常,会形成异常链:
try: 1/0 except Exception as E: raise TypeError('Bad')
将会报告两个异常,并提示处理异常E的时候,触发了另外一个异常TypeError。
Traceback (most recent call last): File "g:/pycode/list.py", line 2, in <module> 1/0 ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "g:/pycode/list.py", line 4, in <module> raise TypeError('Bad') TypeError: Bad
使用from关键字,可让关系更加明确。
try: 1/0 except Exception as E: raise TypeError('Bad') from E
下面是错误报告:
Traceback (most recent call last): File "g:/pycode/list.py", line 2, in <module> 1/0 ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "g:/pycode/list.py", line 4, in <module> raise TypeError('Bad') from E TypeError: Bad
实际上,使用from关键字的时候,会将E的异常对象附加到TypeError的__cause__
属性上。
但不管如何,这里都触发了多个异常。在python 3.3版本,可使用from None
的方式来掩盖异常的来源,也就是禁止输出异常E,中止异常链:
try: 1/0 except Exception as E: raise TypeError('Bad') from None
错误报告以下:
Traceback (most recent call last): File "g:/pycode/list.py", line 4, in <module> raise TypeError('Bad') from None TypeError: Bad
可见,异常信息中少了不少内容。
assert断言经常使用于调试。用法以下:
assert test, data
它实际上等价因而条件判断的raise。它等价于下面的方式:
if __debug__: if not test: raise AssertionError(data)
若是条件test的测试为真,就跳过,不然就抛出异常。这个异常是经过AssertionError类构造的,构造异常对象的参数是data。data会放进名为args的元组属性中。
try: assert False,"something wrong" except Exception as E: print(E.args)
一样,assert产生的是名为AssertionError的异常,若是不捕获这个AssertionError异常,程序将会终止。
除了调试,assert还偶尔用来判断必要的条件,不知足条件就异常,以便让程序更加健壮。例如:
def f(x): assert x >= 0, "x must great or equal than 0" return x ** 2 print(f(2)) print(f(0)) print(f(-2)) # 触发AssertionError异常
须要注意的是,写assert的测试条件时,测试结果为假才触发异常。因此,应该以if not true的角度去考虑条件,或者以unless的角度考虑。或者说,后面触发的异常信息,和测试条件应该是正相关的,例如示例中异常信息的说法是x必须大于等于0,和测试条件x >= 0
是正相关的。
assert还经常使用于父类方法的某些方法中,这些方法要求子类必须重写父类的方法。因而:
class cls: ... def f(self): assert False, "you must override method: f"
此外,assert不该该用来触发那些python早已经定义好的异常。例如索引越界、类型错误等等。这些python已经定义好的异常,咱们再去用AssertionError触发,这是彻底多余的。例如:
def f(obj,index): assert index > len(obj), "IndexError" return obj[index]
该函数用来收集正在处理的异常的信息。
它返回一个包含3个值的元组(type, value, traceback)
,它们是当前正在处理的异常的信息。若是没有正在处理的异常,则返回3个None组成的元组。
其中:
看一个示例便可知道。
class General(Exception):pass def raise0(): x = General() raise x try: raise0() except Exception: import sys print(sys.exc_info())
执行结果:
(<class '__main__.General'>, General(), <traceback object at 0x0388F2B0>)
结果很明显,第一个返回值是异常类General,第二个返回值是抛出的异常类的实例对象,第三个返回值是traceback对象。
实际上,当须要获取当前处理的异常类时,还能够经过异常对象的__class__
来获取,由于异常对象能够在except/as中赋值给变量:
class General(Exception):pass def raise0(): x = General() raise x try: raise0() except Exception as E: import sys print(sys.exc_info()[0]) print(E.__class__)
它们的的结果是彻底同样的:
<class '__main__.General'> <class '__main__.General'>
何时要获取异常类的信息?当except所监视的异常类比较大范围,同时又想知道具体的异常类。好比,except:
或except Exception:
这两种监视的异常范围都很是大,前者会监视BaseException,也就是python的全部异常,后者监视的是Exception,也就是python的全部普通的异常。正由于监视范围太大,致使不知道具体是抛出的是哪一个异常。
错误都是异常,但异常并不必定都是错误。
很常见的,文件结尾的EOF在各类语言中它都定义为异常,是异常就能被触发捕获,但在逻辑上却不认为它是错误。
除此以外,还有操做系统的异常,好比sys.exit()引起的SystemeExit异常,ctrl+c引起的的中断异常KeyboardInterrupt都属于异常,但它们和普通的异常不同。并且python中的普通异常都继承字Exception类,但SystemExit却并不是它的子类,而是BaseException的子类。因此能经过空的except:
捕获到它,却不能经过except Exception:
来捕获。
全部异常类都继承自Exception,要编写自定义的异常时,要么直接继承该类,要么继承该类的某个子类。
例如,下面定义三个异常类,General类继承Exception,另外两个继承General类,表示这两个是特定的、更具体的异常类。
class General(Exception):pass class Specific1(General): pass class Specific2(General): pass def raise0(): x = General() raise x def raise1(): x = Specific1() raise x def raise2(): x = Specific2() raise x
测试下:
for func in (raise0, raise1, raise2): try: func() except General as E: import sys print("caught: ", E.__class__)
执行结果:
caught: <class '__main__.General'> caught: <class '__main__.Specific1'> caught: <class '__main__.Specific2'>
前面说过,except监视父类异常的时候,也会捕获该类的子类异常。正如这里监视的是Gereral类,但触发了Specific子类异常也会被捕获。
这是很是常见的陷阱。有两种异常嵌套的方式:try的嵌套;代码块的异常嵌套(好比函数嵌套)。不管是哪一种嵌套模式,异常都只在最近(或者说是最内层)的代码块中被处理,可是finally块是全部try都会执行的。
第一种try的嵌套模式:
try: try: (1) except xxx: (2) finally: (3) except yyy: ... finally: (4)
若是在(1)处抛出了异常,不管yyy是否匹配这个异常,只要xxx能匹配这个异常,就会执行(2)。但(3)、(4)这两个finally都会执行。
第二种代码块嵌套,常见的是函数调用的嵌套,这种状况可能会比较隐式。例如:
def action2(): print(1 + []) def action1(): try: action2() except TypeError: print('inner try') try: action1() except TypeError: print('outer try')
执行结果:
inner try
上面的action2()会抛出一个TypeError的异常。在action1()中用了try包围action2()的调用,因而action2()的异常汇报给action1()层,而后被捕获。
可是在最外面,使用try包围action1()的调用,看上去异常也会被捕获,但实际上并不会,由于在action2()中就已经经过except处理好了异常,而处理过的异常将再也不是异常,不会再触发外层的异常,因此上面不会输出"outer try"。
在考虑异常捕获的时候,须要注意几点:
第三点很容易理解,有些异常会致使程序没法进行后续的运行,改中断仍是得中断。
对于第一点,可能经常使用的大范围异常监视就是下面两种方式:
except: except Exception:
这两种方式监视的范围都太大了,好比有些不想处理的异常也会被它们监视到。更糟糕的多是本该汇报给上层的异常,结果却被这种大范围捕获了。例如:
def func(): try: ... except: ... try: func() except IndexErro: ...
原本是想在外层的try中明确捕获func触发的IndexError异常的,可是func()内却使用了空的except:
,使得异常直接在这里被处理,外层的try永远也捕获不到任何该函数的异常。
关于第二点,不该该监视范围过小的异常。范围小,意味着监视的异常太过具体,太过细致,这种监视方式虽然精确,但却不利于维护。例如E1异常类有2个子异常类E二、E3,在代码中监视了E二、E3,但若是将来又添加了一个E1的子异常类E4,那么又得去改代码让它监视E4。若是代码是写给本身用的倒无所谓,但若是像通用模块同样交给别人用的,这意味着让别的模块使用者也去改代码。
在前面设计异常类的时候,老是使用pass跳过类代码体。但却仍然能使用这个类做为异常类,由于它继承了Exception,在Exception中有相关代码能够输出异常信息。
前面说过,在构造异常类的时候能够传递参数,它们会放进异常实例对象的args属性中:
try: raise IndexError("something wrong") except Exception as E: print(E.args) try: assert False,"something wrong too" except Exception as E: print(E.args) I = IndexError('text') print(I.args)
对于用户自定义的类,也同样如此:
class MyError(Exception):pass try: raise MyError('something wrong') except MyError as E: print(E.args)
不只如此,虽然异常实例对象是一个对象,但若是直接输出实例对象,那么获得的结果将是给定的异常信息,只不过它不在元组中。
I = IndexError("index wrong") print(I) # 输出"index wrong"
很容易想到,这是由于Exception类中重写了__str__
或者__repr__
中的某一个或两个都重写了。
因而,自定义异常类的时候,也能够重写这两个中的一个,从而能够定制属于本身的异常类的输出信息。通常来讲只重写__str__
,由于Exception中也是重写该类,且它的优先级高于__repr__
。
例以下面自定义的异常类。固然,这个示例的效果很是简陋,但已足够说明问题。
class MyError(Exception): def __str__(self): return 'output this message for something wrong' try: raise MyError("hahhaha") except MyError as E: print(E)
输出结果:
output this message for something wrong
自定义异常类的时候,能够重写构造方法__init__()
,这样raise异常的时候,能够指定构造的数据。并且更进一步的,还能够重写__str__
来自定义异常输出。
例如,格式化文件的程序中定义一个异常类,用来提示解析到哪一个文件的哪一行出错。
class MyError(Exception): def __init__(self,line,file): self.line = line self.file = file def __str__(self): return "format failed: %s at %s" % (self.file, self.line) def format(): ... raise MyError(42, "a.json") ... try: format() except MyError as E: print(E)
异常类既然是类,说明它能够像普通类同样拿来应用。好比添加其它类方法。
例如,能够将异常信息写入到文件中。只需提供一个向文件写入的方法,并在except的语句块中调用这个方法便可。
class MyError(Exception): def __init__(self, line, file): self.line = line self.file = file def logerr(self): with open('Error.log', 'a') as logfile: print(self, file=logfile) def __str__(self): return "format failed: %s at %s" % (self.file, self.line) def format(): raise MyError(42, "a.json") try: format() except MyError as E: E.logerr()