文章目錄
- 簡介
- Python中的内置異常類
- 文法錯誤
- 異常
- 異常處理
- 抛出異常
- 異常鍊
- 自定義異常
- finally
和其他的語言一樣,Python中也有異常和錯誤。在 Python 中,所有異常都是
BaseException
的類的執行個體。 今天我們來詳細看一下Python中的異常和對他們的處理方式。
Python中所有異常類都來自BaseException,它是所有内置異常的基類。
雖然它是所有異常類的基類,但是對于使用者自定義的類來說,并不推薦直接繼承BaseException,而是繼承Exception.
先看下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
其中BaseException,Exception,ArithmeticError,BufferError,LookupError 主要被作為其他異常的基類。
在Python中,對于異常和錯誤通常可以分為兩類,第一類是文法錯誤,又稱解析錯誤。也就是代碼還沒有開始運作,就發生的錯誤。
其産生的原因就是編寫的代碼不符合Python的語言規範:
>>> while True print('Hello world')
File "<stdin>", line 1while True print('Hello world') ^SyntaxError: invalid syntax
上面代碼原因是 print 前面少了 冒号。
即使我們的程式符合python的文法規範,但是在執行的時候,仍然可能發送錯誤,這種在運作時發送的錯誤,叫做異常。
看一下下面的異常:
>>> 10 * (1/0)Traceback (most recent call last):
File "<stdin>", line 1, in <module>ZeroDivisionError: division by zero>>> 4 + spam*3Traceback (most recent call last):
File "<stdin>", line 1, in <module>NameError: name 'spam' is not defined>>> '2' + 2Traceback (most recent call last):
File "<stdin>", line 1, in <module>TypeError: Can't convert 'int' object to str implicitly
程式發生了異常之後該怎麼處理呢?
我們可以使用try except 語句來捕獲特定的異常。
>>> while True:... try:... x = int(input("Please enter a number: "))... break... except ValueError:... print("Oops! That was no valid number. Try again...")...
上面代碼的執行流程是,首先執行try中的子語句,如果沒有異常發生,那麼就會跳過except,并完成try語句的執行。
如果try中的子語句中發生了異常,那麼将會跳過try子句中的後面部分,進行except的異常比對。如果比對成功的話,就會去執行except中的子語句。
如果發生的異常和 except 子句中指定的異常不比對,則将其傳遞到外部的
try
語句中。
一個try中可以有多個except 子句,我們可以這樣寫:
try:raise cls()except D:print("D")except C:print("C")except B:print("B")
一個except也可以帶多個異常:
... except (RuntimeError, TypeError, NameError):... pass
except 子句還可以省略異常名,用來比對所有的異常:
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise
try
…
except
語句有一個可選的 else 子句,在使用時必須放在所有的 except 子句後面。對于在 try 子句不引發異常時必須執行的代碼來說很有用。 例如:
for arg in sys.argv[1:]:try:f = open(arg, 'r')except OSError:print('cannot open', arg)else:print(arg, 'has', len(f.readlines()), 'lines')f.close()
except可以指定異常變量的名字
instance
,這個變量代表這個異常執行個體。
我們可以通過instance.args來輸出異常的參數。
同時,因為異常執行個體定義了
__str__()
,是以可以直接使用print來輸出異常的參數。而不需要使用
.args
。
我們看一個例子:
>>> try:... raise Exception('spam', 'eggs')... except Exception as inst:... print(type(inst)) # the exception instance... print(inst.args) # arguments stored in .args... print(inst) # __str__ allows args to be printed directly,... # but may be overridden in exception subclasses... x, y = inst.args # unpack args... print('x =', x)... print('y =', y)...<class 'Exception'>('spam', 'eggs')('spam', 'eggs')x = spam
y = eggs
上面的例子中,我們在try字句中抛出了一個異常,并且指定了2個參數。
我們可以使用raise語句來抛出異常。
>>> raise NameError('HiThere')Traceback (most recent call last):
File "<stdin>", line 1, in <module>NameError: HiThere
raise的參數是一個異常,這個異常可以是異常執行個體或者是一個異常類。
注意,這個異常類必須是 Exception
的子類。
如果傳遞的是一個異常類,那麼将會調用無參構造函數來隐式執行個體化:
raise ValueError # shorthand for 'raise ValueError()'
如果我們捕獲了某些異常,但是又不想去處理,那麼可以在except語句中使用raise,重新抛出異常。
>>> try:... raise NameError('HiThere')... except NameError:... print('An exception flew by!')... raise...An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>NameError: HiThere
如果我們通過except捕獲一個異常A之後,可以通過raise語句再次抛出一個不同的異常類型B。
那麼我們看到的這個異常資訊就是B的資訊。但是我們并不知道這個異常B是從哪裡來的,這時候,我們就可以用到異常鍊。
異常鍊就是抛出異常的時候,使用raise from語句:
>>> def func():... raise IOError...>>> try:... func()... except IOError as exc:... raise RuntimeError('Failed to open database') from exc...Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in func
OSError
The above exception was the direct cause of the following exception:Traceback (most recent call last):
File "<stdin>", line 4, in <module>RuntimeError: Failed to open database
上面的例子中,我們在捕獲IOError之後,又抛出了RuntimeError,通過使用異常鍊,我們很清晰的看出這兩個異常之間的關系。
預設情況下,如果異常是從except 或者 finally 中抛出的話,會自動帶上異常鍊資訊。
如果你不想帶上異常鍊,那麼可以
from None
try:open('database.sqlite')except IOError:raise RuntimeError from NoneTraceback (most recent call last):
File "<stdin>", line 4, in <module>RuntimeError
使用者可以繼承 Exception 來實作自定義的異常,我們看一些自定義異常的例子:
class Error(Exception):"""Base class for exceptions in this module."""passclass InputError(Error):"""Exception raised for errors in the input.
Attributes:
expression -- input expression in which the error occurred
message -- explanation of the error
"""def __init__(self, expression, message):self.expression = expression
self.message = messageclass TransitionError(Error):"""Raised when an operation attempts a state transition that's not
allowed.
Attributes:
previous -- state at beginning of transition
next -- attempted new state
message -- explanation of why the specific transition is not allowed
"""def __init__(self, previous, next, message):self.previous = previous
self.next = nextself.message = message
try語句可以跟着一個finally語句來實作一些收尾操作。
>>> try:... raise KeyboardInterrupt... finally:... print('Goodbye, world!')...Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
finally
子句将作為
try
語句結束前的最後一項任務被執行, 無論try中是否産生異常,finally語句中的代碼都會被執行。
如果
finally
子句中包含一個
return
語句,則傳回值将來自
finally
子句的某個
return
語句的傳回值,而非來自
try
子句的
return
語句的傳回值。
>>> def bool_return():... try:... return True... finally:... return False...>>> bool_return()False