Subsections

- 8.1 語法錯誤
- 8.2 例外(Exceptions)情形
- 8.3 例外(Exceptions)情形的處理
- 8.4 如何引發例外(Exceptions)
- 8.5 自製一個例外情形
- 8.6 定義善後的動作

#
8. 程式錯誤與例外(Exceptions)情形

至此為止，我們都只有稍稍的提到錯誤訊息。但是如果你有試著執行上面的範例的話，你可能注意到，基本上錯誤的情況可以分成兩類：語法錯誤 ( syntax errors ) 以及例外情況 ( exceptions )。

#
8.1 語法錯誤

語法錯誤也叫做分析時的錯誤(parsing errors)，大概是一般在學Python時最常見到的直譯器所發出來的抱怨：

      >>> while 1 print 'Hello world'
        File "<stdin>", line 1
          while 1 print 'Hello world'
                      ^
      SyntaxError: invalid syntax

Python分析器(parser)會在印出錯誤的行，並且用一個向上的箭號指出最早發現錯誤的地方，而這個錯誤是發生(至少是被發現)在這個箭號所指的單元(token) 之前。在我們的例子裡面：錯誤發生在 `print` 這個關鍵字，因為前面應該有一個 ( "`:`" ) 。錯誤信息裡面也包含檔案名稱以及行數，所以你可以很快知道要到哪裡去找錯。

#
8.2 例外(Exceptions)情形

有的時候，甚至當你的語法完全正確時，當你執行程式時仍然會出錯。這種在程式執行階段發生的錯誤叫做例外情形 ( exceptions ) ，並且會造成程式致命的終止(無法執行下去)。你待會就會知道在Python裡面要怎樣處理這樣的狀況，但是我們先來看這樣的狀況下會造成什麼錯誤信息：

      >>> 10 * (1/0)
      Traceback (innermost last):
        File "<stdin>", line 1
      ZeroDivisionError: integer division or modulo
      >>> 4 + spam*3
      Traceback (innermost last):
        File "<stdin>", line 1
      NameError: spam
      >>> '2' + 2
      Traceback (innermost last):
        File "<stdin>", line 1
      TypeError: illegal argument type for built-in operation

在這些錯誤信息的最後一行都是解釋到底發生了什麼事。例外情況(Exception)有很多種類型，類型的名稱也在錯誤信息之中，在上面的例子裡面，exception的類型分別是： `ZeroDivisionError`, `NameError` 以及 `TypeError`. 。對於內建的exception來說，這些印出來的字串都是這些內建的exception類型的真正類型名稱，但是對於使用者自己自定的exception類型就不一定了(雖然這是一個有用的約定俗成的習慣)。這些標準的exception名稱也正是他們內建的指稱(identifiers) (不是正式的關鍵字)。

這一行其他部分則是有關這個exception類型的詳細解釋，其意義則是依照exception的類型而有不同。

在錯誤信息最後一行之前的部分則是顯示了這個exception發生時的狀況，也就是記憶體堆積(stack)的內容追朔(backtrace)。一般來說這個這個部分包含了stack backtrace的來源行數，但是這並不代表是在從標準輸入讀入時候的行數。

在Python程式庫參考手冊中( Python Library Reference )詳細列出了所有的內建exception及其說明。

#
8.3 例外(Exceptions)情形的處理

我們可以寫一個程式來處理某些的exception。請看下面程式範例，我們要求使用者輸入一個有效的數字，直到所輸入的數字真正有效為止。但是使用者也可以中止這個程式(用 `Control-C` 或者是任何作業系統支援的方式)。值得注意的是，使用者主動中止程式事實上是使用者引發一個 `KeyboardInterrupt` 的exception。

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

這個 `try` 敘述的作用如下：

- 首先，try之後的敘述( try clause ，在 `try` 及 `except` 這兩個字之中所有的敘述)都會被執行。

- 如果沒有發生任何exception， except之後的敘述( except clause )會自動被忽略，整個 `try` 敘述就算是執行完畢。

- 如果當執行try之後的敘述時發生了exception，錯誤地方之後的敘述就不會被執行。然後如果這個exception的類型有某一個適合的 `except` 關鍵字之後的類型的話，就會執行這一個except之後的敘述，然後程式從整個 `try` 敘述之後的地方開始執行。

- 如果所發生的exception在except關鍵字之後找不到相對應的類型時，系統會將這個類型傳給外面一層的 `try` 敘述。如果外層的exception處理機制不存在的話，這就是一個沒有被處理的exception( unhandled exception )，然後整個程式會中斷，並出現上面出現的錯誤程式。

一個 `try` 敘述可以包含許多的except 部分來處理各種不同的exception，但是最多只有一個handler(譯：exception之後的敘述)會真正被執行。Handlers 只處理在所對應的 `try` 部分發生的exception，其他的 try 部分發生的exception則不在處理範圍。一個except子句可以處理一個以上的exception，只要用list括弧把它們括起來。例如：

      ... except (RuntimeError, TypeError, NameError):
      ...     pass

最後的一個 except 可以不寫出exception 類型的名稱，這就當作是一個外卡(wildcard，譯：處理所有的exception)來使用。當使用時要特別的小心，因為如果你很有可能就把一個應該被注意的程式錯誤給隱藏起來了。你也可以在這個except子句裡面印出一個錯誤信息，然後把這個exception再丟(raise)出去(讓呼叫你程式的人來處理這個exception)。

      import string, sys

      try:
          f = open('myfile.txt')
          s = f.readline()
          i = int(string.strip(s))
      except IOError, (errno, strerror):
          print "I/O error(%s): %s" % (errno, strerror)
      except ValueError:
          print "Could not convert data to an integer."
      except:
          print "Unexpected error:", sys.exc_info()[0]
          raise

這個 `try` ... `except` 的敘述有一個可有可無的else子句( else clause )可以使用，當這個子句存在時，必須是放在所有的except clauses的後面。這個子句裡的敘述是當try子句沒有發生任何exception時，一定要執行的敘述。請看例子：

      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` 敘述中保護的程式碼所引發的exception。

當一個exception 發生時，exception本身有一個 連帶的值，也叫做這個exception的參數( argument )。至於這個參數是否存在以及其型態，則是由exception的類型所決定。對於有這個參數存在的exception類型來說，你可以在except clause的後面加入一個名稱(或是list)來接收這個參數的值。請看下例：

      >>> try:
      ...     spam()
      ... except NameError, x:
      ...     print 'name', x, 'undefined'
      ...
      name spam undefined

如果一個exception 有一個參數的話，當它在沒有被處理，當作錯誤信息印出來的時候，就會成為最後(詳細解釋(`detail'))的一部份。

Exception的處理者(handlers，exception clause)並不只處理在try clause當中所發生的exception，也會處理所有在try clause當中所(直接或間接)呼叫的函式所引發的exception。請看下例：

      >>> def this_fails():
      ...     x = 1/0
      ...
      >>> try:
      ...     this_fails()
      ... except ZeroDivisionError, detail:
      ...     print 'Handling run-time error:', detail
      ...
      Handling run-time error: integer division or modulo

#
8.4 如何引發例外(Exceptions)

使用 `raise` 敘述可以引發一個特定的 exception，例如：

      >>> raise NameError, 'HiThere'
      Traceback (innermost last):
        File "<stdin>", line 1
      NameError: HiThere

`raise` 的第一個參數是想要引發的exception的類型，第二個參數(可有可無)則是指定這個exception的參數值。

#
8.5 使用者自訂的例外(Exceptions)

程式設計師可以自己命名自己想要的excetion，其方法是指定一個字串給一個變數，或者是自己創造一個新的exception類別來。舉例說明：

      >>> class MyError:
      ...     def __init__(self, value):
      ...         self.value = value
      ...     def __str__(self):
      ...         return `self.value`
      ...
      >>> try:
      ...     raise MyError(2*2)
      ... except MyError, e:
      ...     print 'My exception occurred, value:', e.value
      ...
      My exception occurred, value: 4
      >>> raise MyError, 1
      Traceback (innermost last):
        File "<stdin>", line 1
      __main__.MyError: 1

許多標準的module都自己自訂其exception來報回(report)在他們自己所定義的函式裡面所發生的錯誤。

有關於classes 更多的討論請看第九章 ``類別''。

#
8.6 定義善後的動作

在 `try` 敘述的機制裡面有一個可有可無的子句(optional clause)，其功用是在定義不管什麼情況發生下，你都得要做的清除善後的工作。 舉例來說：

      >>> try:
      ...     raise KeyboardInterrupt
      ... finally:
      ...     print 'Goodbye, world!'
      ...
      Goodbye, world!
      Traceback (innermost last):
        File "<stdin>", line 2
      KeyboardInterrupt

這個 finally clause 不管你的程式在try裡面是否有任何的exception發生都會被執行。當exception發生時，程式會執行finally clause之後再引發這個exception。當程式的 `try` try部分因為 `break` 或 `return` 而離開時，finally clause也一樣會在離開的時候(``on the way out'')被執行。

一個 `try` 敘述機制應該要有一個或多個except clauses，或者是有一個 finally clause，但是不能兩個都有。

請看關於此文件… 裡面有關如何給我們建議的說明。