SICP Python 描述 3.4 異常

3.4 異常

來(lái)源:3.4 Exceptions

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

程序員必須總是留意程序中可能出現(xiàn)的錯(cuò)誤。例子數(shù)不勝數(shù):一個(gè)函數(shù)可能不會(huì)收到它預(yù)期的信息,必需的資源可能會(huì)丟失,或者網(wǎng)絡(luò)上的連接可能丟失。在設(shè)計(jì)系統(tǒng)時(shí),程序員必須預(yù)料到可能產(chǎn)生的異常情況并且采取適當(dāng)?shù)卮胧﹣?lái)處理它們。

處理程序中的錯(cuò)誤沒(méi)有單一的正確方式。為提供一些持久性服務(wù)而設(shè)計(jì)的程序,例如 Web 服務(wù)器 應(yīng)該對(duì)錯(cuò)誤健壯,將它們記錄到日志中為之后考慮,而且在盡可能長(zhǎng)的時(shí)間內(nèi)繼續(xù)接受新的請(qǐng)求。另一方面,Python 解釋器通過(guò)立即終止以及打印錯(cuò)誤信息來(lái)處理錯(cuò)誤,便于程序員在錯(cuò)誤發(fā)生時(shí)處理它。在任何情況下,程序員必須決定程序如何對(duì)異常條件做出反應(yīng)。

異常是這一節(jié)的話(huà)題,它為程序的錯(cuò)誤處理提供了通用的機(jī)制。產(chǎn)生異常是一種技巧,終止程序正常執(zhí)行流,發(fā)射異常情況產(chǎn)生的信號(hào),并直接返回到用于響應(yīng)異常情況的程序的封閉部分。Python 解釋器每次在檢測(cè)到語(yǔ)句或表達(dá)式錯(cuò)誤時(shí)拋出異常。用戶(hù)也可以使用raiseassert語(yǔ)句來(lái)拋出異常。

拋出異常。異常是一個(gè)對(duì)象實(shí)例,它的類(lèi)直接或間接繼承自BaseException類(lèi)。第一章引入的assert語(yǔ)句產(chǎn)生AssertionError類(lèi)的異常。通常,異常實(shí)例可以使用raise語(yǔ)句來(lái)拋出。raise語(yǔ)句的通用形式在 Python 文檔中描述。raise的最常見(jiàn)的作用是構(gòu)造異常實(shí)例并拋出它。

>>> raise Exception('An error occurred')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: an error occurred

當(dāng)異常產(chǎn)生時(shí),當(dāng)前代碼塊的語(yǔ)句不會(huì)繼續(xù)執(zhí)行。除非異常被解決了(下面會(huì)描述),解釋器會(huì)直接返回到“讀取-求值-打印”交互式循環(huán)中,或者在 Python 以文件參數(shù)啟動(dòng)的情況下會(huì)完全終止。此外,解釋器會(huì)打印?;厮荩墙Y(jié)構(gòu)化的文本塊,描述了執(zhí)行分支中的一系列嵌套的活動(dòng)函數(shù),它們是異常產(chǎn)生的位置。在上面的例子中,文件名稱(chēng)<stdin>表示異常由用戶(hù)在交互式會(huì)話(huà)中產(chǎn)生,而不是文件中的代碼。

處理異常。異常可以使用封閉的try語(yǔ)句來(lái)處理。try語(yǔ)句由多個(gè)子句組成,第一個(gè)子句以try開(kāi)始,剩下的以except開(kāi)始。

try:
    <try suite>
except <exception class> as <name>:
    <except suite>
...

當(dāng)try語(yǔ)句執(zhí)行時(shí),<try suite>總是會(huì)立即執(zhí)行。except子句組只在<try suite>執(zhí)行過(guò)程中的異常產(chǎn)生時(shí)執(zhí)行。每個(gè)except子句指定了需要處理的異常的特定類(lèi)。例如,如果<exception class>AssertionError,那么任何繼承自AssertionError的類(lèi)實(shí)例都會(huì)被處理,標(biāo)識(shí)符<name>綁定到所產(chǎn)生的異常對(duì)象上,但是這個(gè)綁定在<except suite>之外并不有效。

例如,我們可以使用try語(yǔ)句來(lái)處理異常,在異常發(fā)生時(shí)將x綁定為0

>>> try:
        x = 1/0
    except ZeroDivisionError as e:
        print('handling a', type(e))
        x = 0
handling a <class 'ZeroDivisionError'>
>>> x
0

try語(yǔ)句能夠處理產(chǎn)生在函數(shù)體中的異常,函數(shù)在<try suite>中調(diào)用。當(dāng)異常產(chǎn)生時(shí),控制流會(huì)直接跳到最近的try語(yǔ)句的能夠處理該異常類(lèi)型的<except suite>的主體中。

>>> def invert(x):
        result = 1/x  # Raises a ZeroDivisionError if x is 0
        print('Never printed if x is 0')
        return result
>>> def invert_safe(x):
        try:
            return invert(x)
        except ZeroDivisionError as e:
            return str(e)
>>> invert_safe(2)
Never printed if x is 0
0.5
>>> invert_safe(0)
'division by zero'

這個(gè)例子表明,invert中的print表達(dá)式永遠(yuǎn)不會(huì)求值,反之,控制流跳到了handler中的except子句組中。將ZeroDivisionError e強(qiáng)制轉(zhuǎn)為字符串會(huì)得到由handler: 'division by zero'返回的人類(lèi)可讀的字符串。

3.4.1 異常對(duì)象

異常對(duì)象本身就帶有屬性,例如在assert語(yǔ)句中的錯(cuò)誤信息,以及有關(guān)異常產(chǎn)生處的信息。用戶(hù)定義的異常類(lèi)可以攜帶額外的屬性。

在第一章中,我們實(shí)現(xiàn)了牛頓法來(lái)尋找任何函數(shù)的零點(diǎn)。下面的例子定義了一個(gè)異常類(lèi),無(wú)論何時(shí)ValueError出現(xiàn),它都返回迭代改進(jìn)過(guò)程中所發(fā)現(xiàn)的最佳猜測(cè)值。數(shù)學(xué)錯(cuò)誤(ValueError的一種)在sqrt在負(fù)數(shù)上調(diào)用時(shí)產(chǎn)生。這個(gè)異常由拋出IterImproveError處理,它將牛頓迭代法的最新猜測(cè)值儲(chǔ)存為參數(shù)。

首先,我們定義了新的類(lèi),繼承自Exception。

>>> class IterImproveError(Exception):
        def __init__(self, last_guess):
            self.last_guess = last_guess

下面,我們定義了IterImprove,我們的通用迭代改進(jìn)算法的一個(gè)版本。這個(gè)版本通過(guò)拋出IterImproveError異常,儲(chǔ)存最新的猜測(cè)值來(lái)處理任何ValueError。像之前一樣,iter_improve接受兩個(gè)函數(shù)作為參數(shù),每個(gè)函數(shù)都接受單一的數(shù)值參數(shù)。update函數(shù)返回新的猜測(cè)值,而done函數(shù)返回布爾值,表明改進(jìn)是否收斂到了正確的值。

>>> def iter_improve(update, done, guess=1, max_updates=1000):
        k = 0
        try:
            while not done(guess) and k < max_updates:
                guess = update(guess)
                k = k + 1
            return guess
        except ValueError:
            raise IterImproveError(guess)

最后,我們定義了find_root,它返回iter_improve的結(jié)果。iter_improve應(yīng)用于由newton_update返回的牛頓更新函數(shù)。newton_update定義在第一章,在這個(gè)例子中無(wú)需任何改變。find_root的這個(gè)版本通過(guò)返回它的最后一個(gè)猜測(cè)之來(lái)處理IterImproveError。

>>> def find_root(f, guess=1):
        def done(x):
            return f(x) == 0
        try:
            return iter_improve(newton_update(f), done, guess)
        except IterImproveError as e:
            return e.last_guess

考慮使用find_root來(lái)尋找2 * x ** 2 + sqrt(x)的零點(diǎn)。這個(gè)函數(shù)的一個(gè)零點(diǎn)是0,但是在任何負(fù)數(shù)上求解它會(huì)產(chǎn)生ValueError。我們第一章的牛頓法實(shí)現(xiàn)會(huì)產(chǎn)生異常,并且不能返回任何零點(diǎn)的猜測(cè)值。我們的修訂版實(shí)現(xiàn)在錯(cuò)誤之前返回了最新的猜測(cè)值。

>>> from math import sqrt
>>> find_root(lambda x: 2*x*x + sqrt(x))
-0.030211203830201594

雖然這個(gè)近似值仍舊距離正確的答案0很遠(yuǎn),一些應(yīng)用更傾向于這個(gè)近似值而不是ValueError。

異常是另一個(gè)技巧,幫助我們將程序細(xì)節(jié)劃分為模塊化的部分。在這個(gè)例子中,Python 的異常機(jī)制允許我們分離迭代改進(jìn)的邏輯,它在try子句組中沒(méi)有發(fā)生改變,以及錯(cuò)誤處理的邏輯,它出現(xiàn)在except子句中。我們也會(huì)發(fā)現(xiàn),異常在使用 Python 實(shí)現(xiàn)解釋器時(shí)是個(gè)非常實(shí)用的特性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、簡(jiǎn)介 Python最強(qiáng)大的結(jié)構(gòu)之一就是它的異常處理能力,所有的標(biāo)準(zhǔn)異常都使用類(lèi)來(lái)實(shí)現(xiàn),都是基類(lèi)Exceptio...
    隨風(fēng)化作雨閱讀 3,152評(píng)論 0 1
  • 定義類(lèi)并創(chuàng)建實(shí)例 在Python中,類(lèi)通過(guò) class 關(guān)鍵字定義。以 Person 為例,定義一個(gè)Person類(lèi)...
    績(jī)重KF閱讀 4,121評(píng)論 0 13
  • http://python.jobbole.com/85231/ 關(guān)于專(zhuān)業(yè)技能寫(xiě)完項(xiàng)目接著寫(xiě)寫(xiě)一名3年工作經(jīng)驗(yàn)的J...
    燕京博士閱讀 7,828評(píng)論 1 118
  • Python異常處理 異常概念: 異常:就是不正常的情況,程序開(kāi)發(fā)過(guò)程中錯(cuò)誤和BUG都是補(bǔ)充正常的情況 異常發(fā)生的...
    youngkun閱讀 983評(píng)論 0 4
  • 異常處理在程序的健壯性上表現(xiàn)的尤為重要, 例1:#!/usr/bin/pythonimport traceback...
    古佛青燈度流年閱讀 1,004評(píng)論 0 1

友情鏈接更多精彩內(nèi)容