首頁 > Python教程 > Python基礎教程 > Python入門教程之錯誤和異常處理

Python入門教程之錯誤和異常處理

時間:2019-08-15    來源:互聯網

8. 錯誤和異常

至今為止還沒有進一步的談論過錯誤信息,不過在你已經試驗過的那些例子中,可能已經遇到過一些。Python 中(至少)有兩種錯誤:語法錯誤和異常( syntax errors 和 exceptions )。

8.1. 語法錯誤

語法錯誤,也被稱作解析錯誤,也許是你學習 Python 過程中最常見抱怨:

 >>> while True print('Hello world')    File "<stdin>", line 1, in ?      while True print('Hello world')                     ^  SyntaxError: invalid syntax  

語法分析器指出錯誤行,并且在檢測到錯誤的位置前面顯示一個小“箭頭”。 錯誤是由箭頭 前面 的標記引起的(或者至少是這么檢測的): 這個例子中,函數 print() 被發現存在錯誤,因為它前面少了一個冒號( ':' )。 錯誤會輸出文件名和行號,所以如果是從腳本輸入的你就知道去哪里檢查錯誤了。

8.2. 異常

即使一條語句或表達式在語法上是正確的,當試圖執行它時也可能會引發錯誤。運行期檢測到的錯誤稱為 異常,并且程序不會無條件的崩潰:很快,你將學到如何在 Python 程序中處理它們。然而,大多數異常都不會被程序處理,像這里展示的一樣最終會產生一個錯誤信息:

 >>> 10 * (1/0)  Traceback (most recent call last):    File "<stdin>", line 1, in ?  ZeroDivisionError: int division or modulo by zero  >>> 4 + spam*3  Traceback (most recent call last):    File "<stdin>", line 1, in ?  NameError: name 'spam' is not defined  >>> '2' + 2  Traceback (most recent call last):    File "<stdin>", line 1, in ?  TypeError: Can't convert 'int' object to str implicitly  

錯誤信息的最后一行指出發生了什么錯誤。異常也有不同的類型,異常類型做為錯誤信息的一部分顯示出來:示例中的異常分別為 零除錯誤( ZeroDivisionError ) ,命名錯誤( NameError) 和 類型錯誤( TypeError )。打印錯誤信息時,異常的類型作為異常的內置名顯示。對于所有的內置異常都是如此,不過用戶自定義異常就不一定了(盡管這是一個很有用的約定)。標準異常名是內置的標識(沒有保留關鍵字)。

這一行后一部分是關于該異常類型的詳細說明,這意味著它的內容依賴于異常類型。

錯誤信息的前半部分以堆棧的形式列出異常發生的位置。通常在堆棧中列出了源代碼行,然而,來自標準輸入的源碼不會顯示出來。

內置的異常 列出了內置異常和它們的含義。

8.3. 異常處理

通過編程處理選擇的異常是可行的。看一下下面的例子:它會一直要求用戶輸入,直到輸入一個合法的整數為止,但允許用戶中斷這個程序(使用 Control-C 或系統支持的任何方法)。注意:用戶產生的中斷會引發一個 KeyboardInterrupt 異常。

 >>> while True:  ...     try:  ...         x = int(input("Please enter a number: "))  ...         break  ...     except ValueError:  ...         print("Oops!  That was no valid number.  Try again...")  ...  

try 語句按如下方式工作。

  • 首先,執行 try 子句 (在 try 和 except 關鍵字之間的部分)。

  • 如果沒有異常發生, except 子句 在 try 語句執行完畢后就被忽略了。

  • 如果在 try 子句執行過程中發生了異常,那么該子句其余的部分就會被忽略。

    如果異常匹配于 except 關鍵字后面指定的異常類型,就執行對應的except子句。然后繼續執行 try 語句之后的代碼。

  • 如果發生了一個異常,在 except 子句中沒有與之匹配的分支,它就會傳遞到上一級 try 語句中。

    如果最終仍找不到對應的處理語句,它就成為一個 未處理異常,終止程序運行,顯示提示信息。

一個 try 語句可能包含多個 except 子句,分別指定處理不同的異常。至多只會有一個分支被執行。異常處理程序只會處理對應的 try 子句中發生的異常,在同一個 try 語句中,其他子句中發生的異常則不作處理。一個 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 IOError:          print('cannot open', arg)      else:          print(arg, 'has', len(f.readlines()), 'lines')          f.close()  

使用 else 子句比在 try 子句中附加代碼要好,因為這樣可以避免 try … except 意外的截獲本來不屬于它們保護的那些代碼拋出的異常。

發生異常時,可能會有一個附屬值,作為異常的 參數 存在。這個參數是否存在、是什么類型,依賴于異常的類型。

在異常名(列表)之后,也可以為 except 子句指定一個變量。這個變量綁定于一個異常實例,它存儲在 instance.args 的參數中。為了方便起見,異常實例定義了 __str__() ,這樣就可以直接訪問過打印參數而不必引用 .args。這種做法不受鼓勵。相反,更好的做法是給異常傳遞一個參數(如果要傳遞多個參數,可以傳遞一個元組),把它綁定到 message 屬性。一旦異常發生,它會在拋出前綁定所有指定的屬性。

 >>> 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 子句中立刻發生的異常,也會處理那些 try 子句中調用的函數內部發生的異常。例如:

 >>> def this_fails():  ...     x = 1/0  ...  >>> try:  ...     this_fails()  ... except ZeroDivisionError as err:  ...     print('Handling run-time error:', err)  ...  Handling run-time error: int division or modulo by zero  

8.4. 拋出異常

raise 語句允許程序員強制拋出一個指定的異常。例如:

 >>> raise NameError('HiThere')  Traceback (most recent call last):    File "<stdin>", line 1, in ?  NameError: HiThere  

要拋出的異常由 raise 的唯一參數標識。它必需是一個異常實例或異常類(繼承自 Exception 的類)。

如果你需要明確一個異常是否拋出,但不想處理它,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 ?  NameError: HiThere  

8.5. 用戶自定義異常

在程序中可以通過創建新的異常類型來命名自己的異常(Python 類的內容請參見 類 )。異常類通常應該直接或間接的從 Exception 類派生,例如:

 >>> class MyError(Exception):  ...     def __init__(self, value):  ...         self.value = value  ...     def __str__(self):  ...         return repr(self.value)  ...  >>> try:  ...     raise MyError(2*2)  ... except MyError as e:  ...     print('My exception occurred, value:', e.value)  ...  My exception occurred, value: 4  >>> raise MyError('oops!')  Traceback (most recent call last):    File "<stdin>", line 1, in ?  __main__.MyError: 'oops!'  

在這個例子中,Exception 默認的 __init__() 被覆蓋。新的方式簡單的創建 value 屬性。這就替換了原來創建 args 屬性的方式。

異常類中可以定義任何其它類中可以定義的東西,但是通常為了保持簡單,只在其中加入幾個屬性信息,以供異常處理句柄提取。如果一個新創建的模塊中需要拋出幾種不同的錯誤時,一個通常的作法是為該模塊定義一個異常基類,然后針對不同的錯誤類型派生出對應的異常子類:

 class Error(Exception):      """Base class for exceptions in this module."""      pass    class 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 = message    class 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 = next          self.message = message  

與標準異常相似,大多數異常的命名都以 “Error” 結尾。

很多標準模塊中都定義了自己的異常,用以報告在他們所定義的函數中可能發生的錯誤。關于類的進一步信息請參見 類 一章。

8.6. 定義清理行為

try 語句還有另一個可選的子句,目的在于定義在任何情況下都一定要執行的功能。例如:

 >>> try:  ...     raise KeyboardInterrupt  ... finally:  ...     print('Goodbye, world!')  ...  Goodbye, world!  KeyboardInterrupt  Traceback (most recent call last):    File "<stdin>", line 2, in ?  

不管有沒有發生異常,finally子句 在程序離開 try 后都一定會被執行。當 try 語句中發生了未被 except 捕獲的異常(或者它發生在 except 或 else 子句中),在 finally 子句執行完后它會被重新拋出。 try 語句經由 break ,continue 或 return 語句退 出也一樣會執行 finally 子句。以下是一個更復雜些的例子:

 >>> def divide(x, y):  ...     try:  ...         result = x / y  ...     except ZeroDivisionError:  ...         print("division by zero!")  ...     else:  ...         print("result is", result)  ...     finally:  ...         print("executing finally clause")  ...  >>> divide(2, 1)  result is 2  executing finally clause  >>> divide(2, 0)  division by zero!  executing finally clause  >>> divide("2", "1")  executing finally clause  Traceback (most recent call last):    File "<stdin>", line 1, in ?    File "<stdin>", line 3, in divide  TypeError: unsupported operand type(s) for /: 'str' and 'str'  

如你所見, finally 子句在任何情況下都會執行。TypeError 在兩個字符串相除的時候拋出,未被 except 子句捕獲,因此在 finally 子句執行完畢后重新拋出。

在真實場景的應用程序中,finally 子句用于釋放外部資源(文件 或網絡連接之類的),無論它們的使用過程中是否出錯。

8.7. 預定義清理行為

有些對象定義了標準的清理行為,無論對象操作是否成功,不再需要該對象的時候就會起作用。以下示例嘗試打開文件并把內容打印到屏幕上。

 for line in open("myfile.txt"):      print(line)  

這段代碼的問題在于在代碼執行完后沒有立即關閉打開的文件。這在簡單的腳本里沒什么,但是大型應用程序就會出問題。with 語句使得文件之類的對象可以 確保總能及時準確地進行清理。

 with open("myfile.txt") as f:      for line in f:          print(line)  

語句執行后,文件 f 總會被關閉,即使是在處理文件中的數據時出錯也一樣。其它對象是否提供了預定義的清理行為要查看它們的文檔。

 

相關推薦
Python入門簡介 Python能做什么?
調用Python 解釋器 什么是Python 解釋器?
Python3 簡介
Python入門教程之流程控制
Python入門教程之數據結構
Python入門教程之模塊
Python入門教程之輸入和輸出
Python入門教程之類
Python入門教程之標準庫概覽
Python入門教程之標準庫瀏覽 – Part II
Python入門教程之虛擬環境和包
Python入門教程之浮點數算法:爭議和限制

精彩推薦

熱門教程

忍者法宝试玩
老澳门即时赔率 安装千里马计划软件 山东十一选五 北京快中彩微信群 3d杀号选胆图 重庆时时彩龙虎3期计划 浙江快乐12选5开奖总汇 安徽11选5 时时彩技巧经验 pk10冠军大小稳赢压发 幸运飞艇计划软件 app 七星江苏麻将外挂 电竞比分网app 下载捕鱼大富翁 河北快3 南通棋牌游戏大厅