? ? ? ? ?python使用被稱為異常的特殊對象來管理程序執行期間發生的錯誤。每當發生讓python不知所措的錯誤時,他都會創建一個異常對象。如果你編寫了處理該異常的代碼,程序將繼續運行;如果你未對異常進行處理,程序將停止,并顯示一個traceback,其中包含有關異常的報告。
? ? ? ? 異常是使用try-except代碼塊處理的。try-except代碼塊讓Python執行指定的操作, 同時告訴Python發生異常時怎么辦。 使用了try-except代碼塊時, 即便出現異常,
程序也將繼續運行: 顯示你編寫的友好的錯誤消息, 而不是令用戶迷惑的traceback。
1處理ZeroDivisionError異常
下面來看一種導致Python引發異常的簡單錯誤。 你可能知道不能將一個數字除以0, 但我們還是讓Python這樣做吧:
division.py
print(5/0)
顯然,Python無法這樣做, 因此你將看到一個traceback:
Traceback (most recent call last):
File "division.py", line 1, in
print(5/0)
?ZeroDivisionError: division by zero
在上述traceback中,?處指出的錯誤ZeroDivisionError是一個異常對象。Python無法按你的要求做時, 就會創建這種對象。 在這種情況下,Python將停止運行程序, 并指出
引發了哪種異常, 而我們可根據這些信息對程序進行修改。 下面我們將告訴Python, 發生這種錯誤時怎么辦; 這樣, 如果再次發生這樣的錯誤, 我們就有備無患了。
2使用try-except代碼塊
當你認為可能發生了錯誤時, 可編寫一個try-except代碼塊來處理可能引發的異常。 你讓Python嘗試運行一些代碼, 并告訴它如果這些代碼引發了指定的異常, 該怎么辦。
處理ZeroDivisionError異常的try-except代碼塊類似于下面這樣:
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
我們將導致錯誤的代碼行print(5/0)放在了一個try代碼塊中。 如果try代碼塊中的代碼運行起來沒有問題,Python將跳過except代碼塊; 如果try代碼塊中的代碼導致了
錯誤,Python將查找這樣的except代碼塊, 并運行其中的代碼, 即其中指定的錯誤與引發的錯誤相同。
在這個示例中,try代碼塊中的代碼引發了ZeroDivisionError異常, 因此Python指出了該如何解決問題的except代碼塊, 并運行其中的代碼。 這樣, 用戶看到的是一條友
好的錯誤消息, 而不是traceback:
You can't divide by zero!
如果try-except代碼塊后面還有其他代碼, 程序將接著運行, 因為已經告訴了Python如何處理這種錯誤。 下面來看一個捕獲錯誤后程序將繼續運行的示例。
3使用異常避免崩潰
發生錯誤時, 如果程序還有工作沒有完成, 妥善地處理錯誤就尤其重要。 這種情況經常會出現在要求用戶提供輸入的程序中; 如果程序能夠妥善地處理無效輸入, 就能再提示用
戶提供有效輸入, 而不至于崩潰。
下面來創建一個只執行除法運算的簡單計算器:
division.py
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
while True:
?first_number = input("\nFirst number: ")
if first_number == 'q':
break
?second_number = input("Second number: ")
if second_number == 'q':
break
?answer = int(first_number) / int(second_number)
print(answer)
在?處, 這個程序提示用戶輸入一個數字, 并將其存儲到變量first_number中; 如果用戶輸入的不是表示退出的q, 就再提示用戶輸入一個數字, 并將其存儲到變
量second_number中( 見?) 。 接下來, 我們計算這兩個數字的商( 即answer, 見?) 。 這個程序沒有采取任何處理錯誤的措施, 因此讓它執行除數為0的除法運算時, 它
將崩潰:
Give me two numbers, and I'll divide them.
Enter 'q' to quit.
First number: 5
Second number: 0
Traceback (most recent call last):
File "division.py", line 9, in
answer = int(first_number) / int(second_number)
ZeroDivisionError: division by zero
程序崩潰可不好, 但讓用戶看到traceback也不是好主意。 不懂技術的用戶會被它們搞糊涂, 而且如果用戶懷有惡意, 他會通過traceback獲悉你不希望他知道的信息。 例如, 他將知
道你的程序文件的名稱, 還將看到部分不能正確運行的代碼。 有時候, 訓練有素的攻擊者可根據這些信息判斷出可對你的代碼發起什么樣的攻擊。
4?else代碼塊
通過將可能引發錯誤的代碼放在try-except代碼塊中, 可提高這個程序抵御錯誤的能力。 錯誤是執行除法運算的代碼行導致的, 因此我們需要將它放到try-except代碼塊
中。 這個示例還包含一個else代碼塊; 依賴于try代碼塊成功執行的代碼都應放到else代碼塊中:
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
while True:
first_number = input("\nFirst number: ")
if first_number == 'q':
break
second_number = input("Second number: ")
?try:
answer = int(first_number) / int(second_number)
?except ZeroDivisionError:
print("You can't divide by 0!")
?else:
print(answer)
我們讓Python嘗試執行try代碼塊中的除法運算( 見?) , 這個代碼塊只包含可能導致錯誤的代碼。 依賴于try代碼塊成功執行的代碼都放在else代碼塊中; 在這個示例中, 如
果除法運算成功, 我們就使用else代碼塊來打印結果( 見?) 。
except代碼塊告訴Python, 出現ZeroDivisionError異常時該怎么辦( 見?) 。 如果try代碼塊因除零錯誤而失敗, 我們就打印一條友好的消息, 告訴用戶如何避免這種錯
誤。 程序將繼續運行, 用戶根本看不到traceback:
Give me two numbers, and I'll divide them.
Enter 'q' to quit.
First number: 5
Second number: 0
You can't divide by 0!
First number: 5
Second number: 2
2.5
First number: q
try-except-else代碼塊的工作原理大致如下:Python嘗試執行try代碼塊中的代碼; 只有可能引發異常的代碼才需要放在try語句中。 有時候, 有一些僅在try代碼塊成功
執行時才需要運行的代碼; 這些代碼應放在else代碼塊中。except代碼塊告訴Python, 如果它嘗試運行try代碼塊中的代碼時引發了指定的異常, 該怎么辦。
通過預測可能發生錯誤的代碼, 可編寫健壯的程序, 它們即便面臨無效數據或缺少資源, 也能繼續運行, 從而能夠抵御無意的用戶錯誤和惡意的攻擊。
5處理FileNotFoundError異常
使用文件時, 一種常見的問題是找不到文件: 你要查找的文件可能在其他地方、 文件名可能不正確或者這個文件根本就不存在。 對于所有這些情形, 都可使用try-except代碼
塊以直觀的方式進行處理。
我們來嘗試讀取一個不存在的文件。 下面的程序嘗試讀取文件alice.txt的內容, 但我沒有將這個文件存儲在alice.py所在的目錄中:
alice.py
filename = 'alice.txt'
with open(filename) as f_obj:
contents = f_obj.read()
Python無法讀取不存在的文件, 因此它引發一個異常:
Traceback (most recent call last):
File "alice.py", line 3, in
with open(filename) as f_obj:
FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'
在上述traceback中, 最后一行報告了FileNotFoundError異常, 這是Python找不到要打開的文件時創建的異常。 在這個示例中, 這個錯誤是函數open()導致的, 因此要處理
這個錯誤, 必須將try語句放在包含open()的代碼行之前:
filename = 'alice.txt'
try:
with open(filename) as f_obj:
contents = f_obj.read()
except FileNotFoundError:
msg = "Sorry, the file " + filename + " does not exist."
print(msg)
在這個示例中,try代碼塊引發FileNotFoundError異常, 因此Python找出與該錯誤匹配的except代碼塊, 并運行其中的代碼。 最終的結果是顯示一條友好的錯誤消息, 而
不是traceback:
Sorry, the file alice.txt does not exist.
如果文件不存在, 這個程序什么都不做, 因此錯誤處理代碼的意義不大。 下面來擴展這個示例, 看看在你使用多個文件時, 異常處理可提供什么樣的幫助。
6分析文本
你可以分析包含整本書的文本文件。 很多經典文學作品都是以簡單文本文件的方式提供的, 因為它們不受版權限制。 本節使用的文本來自項目Gutenberg(http://gutenberg.org/) ,
這個項目提供了一系列不受版權限制的文學作品, 如果你要在編程項目中使用文學文本, 這是一個很不錯的資源。
下面來提取童話Alice in Wonderland的文本, 并嘗試計算它包含多少個單詞。 我們將使用方法split(), 它根據一個字符串創建一個單詞列表。 下面是對只包含童話名"Alice
in Wonderland"的字符串調用方法split()的結果:
>>> title = "Alice in Wonderland"
>>> title.split()
['Alice', 'in', 'Wonderland']
方法split()以空格為分隔符將字符串分拆成多個部分, 并將這些部分都存儲到一個列表中。 結果是一個包含字符串中所有單詞的列表, 雖然有些單詞可能包含標點。 為計算
Alice in Wonderland包含多少個單詞, 我們將對整篇小說調用split(), 再計算得到的列表包含多少個元素, 從而確定整篇童話大致包含多少個單詞:
filename = 'alice.txt'
try:
with open(filename) as f_obj:
contents = f_obj.read()
except FileNotFoundError:
msg = "Sorry, the file " + filename + " does not exist."
print(msg)
else:
#計算文件大致包含多少個單詞
?words = contents.split()
?num_words = len(words)
?print("The file " + filename + " has about " + str(num_words) + " words.")
我們把文件alice.txt移到了正確的目錄中, 讓try代碼塊能夠成功地執行。 在?處, 我們對變量contents( 它現在是一個長長的字符串, 包含童話Alice in Wonderland的全部文
本) 調用方法split(), 以生成一個列表, 其中包含這部童話中的所有單詞。 當我們使用len()來確定這個列表的長度時, 就知道了原始字符串大致包含多少個單詞( 見
?) 。 在?處, 我們打印一條消息, 指出文件包含多少個單詞。 這些代碼都放在else代碼塊中, 因為僅當try代碼塊成功執行時才執行它們。 輸出指出了文件alice.txt包含多少
個單詞:
The file alice.txt has about 29461 words.
這個數字有點大, 因為這里使用的文本文件包含出版商提供的額外信息, 但與童話Alice in Wonderland的長度相當一致。
7使用多個文件
下面多分析幾本書。 這樣做之前, 我們先將這個程序的大部分代碼移到一個名為count_words()的函數中, 這樣對多本書進行分析時將更容易:
word_count.py
def count_words(filename):
?"""計算一個文件大致包含多少個單詞"""
try:
with open(filename) as f_obj:
contents = f_obj.read()
except FileNotFoundError:
msg = "Sorry, the file " + filename + " does not exist."
print(msg)
else:
#計算文件大致包含多少個單詞
words = contents.split()
num_words = len(words)
print("The file " + filename + " has about " + str(num_words) +
" words.")
filename = 'alice.txt'
count_words(filename)
這些代碼大都與原來一樣, 我們只是將它們移到了函數count_words()中, 并增加了縮進量。 修改程序的同時更新注釋是個不錯的習慣, 因此我們將注釋改成了文檔字符串,
并稍微調整了一下措辭( 見?) 。
現在可以編寫一個簡單的循環, 計算要分析的任何文本包含多少個單詞了。 為此, 我們將要分析的文件的名稱存儲在一個列表中, 然后對列表中的每個文件都調
用count_words()。 我們將嘗試計算Alice in Wonderland、Siddhartha、Moby Dick和Little Women分別包含多少個單詞, 它們都不受版權限制。 我故意沒有將siddhartha.txt放到
word_count.py所在的目錄中, 讓你能夠看到這個程序在文件不存在時處理得有多出色:
def count_words(filename):
--snip--
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)
文件siddhartha.txt不存在, 但這絲毫不影響這個程序處理其他文件:
The file alice.txt has about 29461 words.
Sorry, the file siddhartha.txt does not exist.
The file moby_dick.txt has about 215136 words.
The file little_women.txt has about 189079 words.
在這個示例中, 使用try-except代碼塊提供了兩個重要的優點: 避免讓用戶看到traceback; 讓程序能夠繼續分析能夠找到的其他文件。 如果不捕獲因找不到siddhartha.txt而引發
的FileNotFoundError異常, 用戶將看到完整的traceback, 而程序將在嘗試分析Siddhartha后停止運行——根本不分析Moby Dick和Little Women。
8失敗時一聲不吭
在前一個示例中, 我們告訴用戶有一個文件找不到。 但并非每次捕獲到異常時都需要告訴用戶, 有時候你希望程序在發生異常時一聲不吭, 就像什么都沒有發生一樣繼續運行。
要讓程序在失敗時一聲不吭, 可像通常那樣編寫try代碼塊, 但在except代碼塊中明確地告訴Python什么都不要做。Python有一個pass語句, 可在代碼塊中使用它來讓Python
什么都不要做:
def count_words(filename):
"""計算一個文件大致包含多少個單詞"""
try:
--snip--
except FileNotFoundError:
?pass
else:
--snip--
filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)
相比于前一個程序, 這個程序唯一不同的地方是?處的pass語句。 現在, 出現FileNotFoundError異常時, 將執行except代碼塊中的代碼, 但什么都不會發生。 這種錯誤
發生時, 不會出現traceback, 也沒有任何輸出。 用戶將看到存在的每個文件包含多少個單詞, 但沒有任何跡象表明有一個文件未找到:
The file alice.txt has about 29461 words.
The file moby_dick.txt has about 215136 words.
The file little_women.txt has about 189079 words.
pass語句還充當了占位符, 它提醒你在程序的某個地方什么都沒有做, 并且以后也許要在這里做些什么。 例如, 在這個程序中, 我們可能決定將找不到的文件的名稱寫入到文
件missing_files.txt中。 用戶看不到這個文件, 但我們可以讀取這個文件, 進而處理所有文件找不到的問題。
10.3.9決定報告哪些錯誤
在什么情況下該向用戶報告錯誤? 在什么情況下又應該在失敗時一聲不吭呢? 如果用戶知道要分析哪些文件, 他們可能希望在有文件沒有分析時出現一條消息, 將其中的原因告
訴他們。 如果用戶只想看到結果, 而并不知道要分析哪些文件, 可能就無需在有些文件不存在時告知他們。 向用戶顯示他不想看到的信息可能會降低程序的可用性。Python的錯誤
處理結構讓你能夠細致地控制與用戶分享錯誤信息的程度, 要分享多少信息由你決定。
編寫得很好且經過詳盡測試的代碼不容易出現內部錯誤, 如語法或邏輯錯誤, 但只要程序依賴于外部因素, 如用戶輸入、 存在指定的文件、 有網絡鏈接, 就有可能出現異常。 憑
借經驗可判斷該在程序的什么地方包含異常處理塊, 以及出現錯誤時該向用戶提供多少相關的信息。