### 8. 錯誤和異常
直到現在,我們還沒有更多的提及錯誤信息,但是如果你真的嘗試了前面的例子,也許你已經見到過一些。Python(至少)有兩種錯誤很容易區分:*語法錯誤* 和*異常*。
### 8.1. 語法錯誤
語法錯誤,或者稱之為解析錯誤,可能是你在學習 Python 過程中最煩的一種:
~~~
>>> while True print('Hello world')
File "<stdin>", line 1, in ?
while True print('Hello world')
^
SyntaxError: invalid syntax
~~~
語法分析器指出了出錯的一行,并且在最先找到的錯誤的位置標記了一個小小的’箭頭’。錯誤是由箭頭*前面* 的標記引起的(至少檢測到是這樣的): 在這個例子中,檢測到錯誤發生在函數[print()](# "print"),因為在它之前缺少一個冒號(':')。文件名和行號會一并輸出,所以如果運行的是一個腳本你就知道去哪里檢查錯誤了。
### 8.2. 異常
即使一條語句或表達式在語法上是正確的,在運行它的時候,也有可能發生錯誤。在執行期間檢測到的錯誤被稱為*異常* 并且程序不會無條件地崩潰:你很快就會知道如何在 Python 程序中處理它們。然而大多數異常都不會被程序處理,導致產生類似下面的錯誤信息:
~~~
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
ZeroDivisionError: division 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](# "ZeroDivisionError")、 [NameError](# "NameError")和[TypeError](# "TypeError")。打印出來的異常類型的字符串就是內置的異常的名稱。這對于所有內置的異常是正確的,但是對于用戶自定義的異常就不一定了(盡管這是非常有用的慣例)。標準異常的名稱都是內置的標識符(不是保留的關鍵字)。
這一行最后一部分給出了異常的詳細信息和引起異常的原因。
錯誤信息的前面部分以堆棧回溯的形式顯示了異常發生的上下文。通常調用棧里會包含源代碼的行信息,但是來自標準輸入的源碼不會顯示行信息。
[*內置的異常*](#) 列出了內置的異常以及它們的含義。
### 8.3. 拋出異常
可以通過編程來選擇處理部分異常。看一下下面的例子,它會一直要求用戶輸入直到輸入一個合法的整數為止,但允許用戶中斷這個程序(使用Control-C或系統支持的任何方法);注意用戶產生的中斷引發的是 [KeyboardInterrupt](# "exceptions.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](#)語句可能有多個子句,以指定不同的異常處理程序。不過至多只有一個處理程序將被執行。處理程序只處理發生在相應 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 子句可以在異常名之后指定一個變量。這個變量將綁定于一個異常實例,同時異常的參數將存放在實例的args中。為方便起見,異常實例定義了[__str__()](# "object.__str__") ,因此異常的參數可以直接打印而不必引用.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 子句中的異常,而且還處理 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](# "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](# "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](# "Exception")默認的[__init__()](# "object.__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
~~~
不管有沒有發生異常,在離開[try](#)語句之前總是會執行*finally*子句。當[try](#)子句中發生了一個異常,并且沒有[except](#)字句處理(或者異常發生在[try](#)或[else](#)子句中),在執行完[finally](#)子句后將重新引發這個異常。[try](#)語句由于[break](#)、[contine](#)或[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.0
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](# "TypeError")異常沒有被[except](#)子句處理,因此在執行[finally](#)子句后被重新引發。
在真實的應用程序中, [finally](#)子句用于釋放外部資源(例如文件或網絡連接),不管資源的使用是否成功。
### 8.7. 清理操作的預定義
有些對象定義了在不需要該對象時的標準清理操作,無論該對象的使用是成功還是失敗。看看下面的示例,它嘗試打開一個文件并打印其內容到屏幕。
~~~
for line in open("myfile.txt"):
print(line, end="")
~~~
這段代碼的問題就是這部分代碼執行完之后它還會讓文件在一段不確定的時間內保持打開狀態。這在簡單的腳本中沒什么,但是在大型應用程序中可能是一個問題。[With](#)語句可以確保像文件這樣的對象總能及時準確地被清理掉。
~~~
with open("myfile.txt") as f:
for line in f:
print(line, end="")
~~~
執行該語句后,文件*f* 將始終被關閉,即使在處理某一行時遇到了問題。提供預定義的清理行為的對象,和文件一樣,會在它們的文檔里說明。
- Python 2 教程
- 1. 吊吊你的胃口
- 2. Python 解釋器
- 3. Python簡介
- 4. 控制流
- 5. 數據結構
- 6. 模塊
- 7. 輸入和輸出
- 8. 錯誤和異常
- 9. 類
- 10. 標準庫概覽
- 11. 標準庫概覽 — 第II部分
- 12.現在怎么辦?
- 13. 交互式輸入的編輯和歷史記錄
- 14. 浮點數運算:問題和局限
- Python 2 標準庫
- 1. 引言
- 2. 內建函數
- 3. 不太重要的內建函數
- 4. 內建的常量
- 5. 內建的類型
- 6. 內建的異常
- 7. String Services
- 8. Data Types
- 9. Numeric and Mathematical Modules
- 10. File and Directory Access
- 11. Data Persistence
- 13. File Formats
- 14. Cryptographic Services
- 15. Generic Operating System Services
- 16. Optional Operating System Services
- 17. Interprocess Communication and Networking
- 18. Internet Data Handling
- 20. Internet Protocols and Support
- 26. Debugging and Profiling
- 28. Python Runtime Services
- Python 2 語言參考
- 1. 簡介
- 2. 詞法分析
- 3. 數據模型
- 4. 執行模型
- 5. 表達式
- 6. 簡單語句
- 7. 復合語句
- 8. 頂層的組件
- 9. 完整的語法規范
- Python 3 教程
- 1. 引言
- 2. Python 解釋器
- 3. Python簡介
- 4. 控制流
- 5. 數據結構
- 6. 模塊
- 7. 輸入和輸出
- 8. 錯誤和異常
- 9. 類
- 10. 標準庫概覽
- 11. 標準庫概覽 — 第II部分
- 12.現在怎么辦?
- 13. 交互式輸入的編輯和歷史記錄
- 14. 浮點數運算:問題和局限