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