CrazyWing:Python自動化運維開發實戰 十七、Python

choubou發表於2021-09-09

導語:

在寫程式碼的時候,經常會遇到異常。

python提供了兩個功能來處理程式在執行中出現的異常和錯誤,可以使用該功能來除錯python程式。

異常處理

斷言(Assertions)

常用異常:

Exception  它可以捕獲任意(絕大部分)異常。

AttributeError 試圖訪問一個物件沒有的樹形,比如foo.x,但是foo沒有屬性x

IOError 輸入/輸出異常;基本上是無法開啟檔案 

ImportError 無法引入模組或包;基本上是路徑問題或名稱錯誤 

IndentationError 語法錯誤(的子類),程式碼沒有正確對齊 

IndexError 下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問x[5]

KeyError 試圖訪問字典裡不存在的鍵 

KeyboardInterrupt Ctrl+C被按下 

NameError 使用一個還未被賦予物件的變數 

SyntaxError Python程式碼非法,程式碼不能編譯(個人認為這是語法錯誤,寫錯了) 

TypeError 傳入物件型別與要求的不符合 

UnboundLocalError 試圖訪問一個還未被設定的區域性變數,基本上是由於另有一個同名的全域性變數,導致你以為正在訪問它 

ValueError 傳入一個呼叫者不期望的值,即使值的型別是正確的 

python標準異常:

CrazyWing:Python開發實戰 十七、Python異常

CrazyWing:Python開發實戰 十七、Python異常

CrazyWing:Python開發實戰 十七、Python異常

什麼是異常?

異常即是一個事件,該事件會在程式執行過程中發生,影響程式的正常執行。 

一般情況下,在Python無法正常處理程式時就會發生一個異常,異常是Python物件,表示一

個錯誤。

當Python指令碼發生異常時我們需要捕獲處理它,否則程式會終止執行。

#!/usr/bin/env python

try:

    print "%d" % (5 / 0)

except ZeroDivisionError:

    print "除數不能為零"

else:

    print "沒有報錯"

print "這是異常之後的程式碼"  #如果沒有上面的異常處理,下面的程式碼是不會執行的

for i in range(10):

    print i

捕捉異常:

try/except語句用來檢測try語句塊中的錯誤,從而讓except語句捕獲異常資訊並處理。 

如果你不想在異常發生時結束你的程式,只需在try裡捕獲它。 

語法: 

    try:

        <語句>                #執行別的程式碼

    except <名字>:

        <語句>                #如果在try部份引發了'name'異常

    except <名字>,<資料>:

        <語句>                #如果引發了'name'異常,獲得附加的資料

    else:

        <語句>                #如果沒有異常發生 

try的工作原理

當開始一個try語句後,python就在當前程式的上下文中作標記,這樣當異常出現時就可

以回到這裡,try子句先執行,接下來會發生什麼依賴於執行時是否出現異常。

1. 如果當try後的語句執行時發生異常,python就跳回到try並執行第一個匹配該異常的

except子句,異常處理完畢,控制流就透過整個try語句(除非在處理異常時又引發新的異

常)。

2. 如果在try後的語句裡發生了異常,卻沒有匹配的except子句,異常將被遞交到上層的

try,或者到程式的最上層(這樣將結束程式,並列印預設的出錯資訊)。

3. 如果在try子句執行時沒有發生異常,python將執行else語句後的語句(如果有else的

話),然後控制流透過整個try語句。

開啟一個檔案,在該檔案中的寫入內容,且並未發生異常:

    try:

        fh = open("testfile", "w")

        fh.write("這是一個測試檔案,用於測試異常!!")

    except IOError:

        print "Error: 沒有找到檔案或讀取檔案失敗"

    else:

        print "內容寫入檔案成功"

        fh.close()

結果:

    # python test.py 

        內容寫入檔案成功

    # cat testfile       # 檢視寫入的內容

        這是一個測試檔案,用於測試異常!! 

開啟一個檔案,在該檔案中的內容寫入內容,但檔案沒有寫入許可權,發生了異常:

    try:

        fh = open("testfile", "w")    

        fh.write("這是一個測試檔案,用於測試異常!!")

    except IOError:

        print "Error: 沒有找到檔案或讀取檔案失敗"

    else:

        print "內容寫入檔案成功"

        fh.close() 

在執行程式碼前為了測試方便,先去掉 testfile 檔案的寫許可權

再執行以上程式碼:

    $ python test.py                                #注意這裡用的是普通使用者

    Error: 沒有找到檔案或讀取檔案失敗

使用except不帶任何異常型別

你可以不帶任何異常型別使用except,如下例項:

     try:

        正常的操作

        ......................

     except:

        發生異常,執行這塊程式碼

        ......................

     else:

        如果沒有異常執行這塊程式碼  

以上方式try-except語句捕獲所有發生的異常。但這不是一個很好的方式,我們不能透過該程式

識別出具體的異常資訊。因為它捕獲所有的異常。

使用except帶多種異常型別

也可以使用相同的except語句來處理多個異常資訊:

    try:

        正常的操作

        ................

    except(Exception1[, Exception2[,...ExceptionN]]]):

       發生以上多個異常中的一個,執行這塊程式碼

       ......................

    else:

        如果沒有異常執行這塊程式碼

try-finally 語句

try-finally 語句無論是否發生異常都將執行最後的程式碼。

    try:

        <語句>

    finally:

        <語句>    #退出try時總會執行

例1:

    try:

        fh = open("testfile", "w")

        fh.write("這是一個測試檔案,用於測試異常!!")

    finally:

        print "Error: 沒有找到檔案或讀取檔案失敗"

例2:

import  time    

try:

    f=file("檔案.py")    

    while True:

        line = f.read()

        if len(line)==0:

            break

        time.sleep(2)

        print line,

finally:

    f.close()

    print "hello"

例3:

    try:

        fh = open("testfile", "w")

        try:

            fh.write("這是一個測試檔案,用於測試異常!!")

        finally:

            print "關閉檔案"

            fh.close()

    except IOError:

        print "Error: 沒有找到檔案或讀取檔案失敗"

異常的引數:

一個異常可以帶上引數,可作為輸出的異常資訊引數。

你可以透過except語句來捕獲異常的引數,如下所示:

     try:

        正常的操作

        ......................

     except ExceptionType, Argument:

        你可以在這輸出 Argument 的值... 

變數接收的異常值通常包含在異常的語句中。在元組的表單中變數可以接收一個或者多個

值。

元組通常包含錯誤字串,錯誤數字,錯誤位置。

以下為單個異常的例項:

    #!/usr/bin/python

    def temp_convert(var):

        try:

            return int(var)

        except ValueError, Argument:

            print "引數沒  有包含數字n", Argument

    # 呼叫函式

    temp_convert("xyz")

以上程式執行結果如下:

    $ python test.py 

        引數沒有包含數字

        invalid literal for int() with base 10: 'xyz'

觸發異常

可以使用raise語句自己觸發異常

raise語法格式:

raise [Exception [, args [, traceback]]]

語句中Exception是異常的型別(例如,NameError)引數是一個異常引數值。該引數是可

選的,如果不提供,異常的引數是"None"。

最後一個引數是可選的(在實踐中很少使用),如果存在,是跟蹤異常物件。

一個異常可以是一個字串,類或物件。 Python的核心提供的異常,大多數都是例項化的

類,這是一個類的例項的引數。

定義一個異常:

def functionName( level ):

    if level < 1:

        raise Exception("Invalid level!", level)

        # 觸發異常後,後面的程式碼就不會再執行

注意:為了能夠捕獲異常,"except"語句必須有用相同的異常來丟擲類物件或者字串。

例如我們捕獲以上異常,"except"語句如下:

    try:

        正常邏輯

    except "Invalid level!":

        觸發自定義異常    

    else:

        其餘程式碼

    #!/usr/bin/python

    def mye( level ):

        if level < 1:

            raise Exception("Invalid level!", level)

            # 觸發異常後,後面的程式碼就不會再執行

    try:

        mye(0)                # 觸發異常

    except "Invalid level!":

        print 1

    else:

        print 2

輸出結果:

    $ python test.py 

    Traceback (most recent call last):

      File "test.py", line 11, in <module>

        mye(0)

      File "test.py", line 7, in mye

        raise Exception("Invalid level!", level)

    Exception: ('Invalid level!', 0)

使用者自定義異常:

透過建立一個新的異常類,程式可以命名它們自己的異常。異常應該是典型的繼承自

Exception類,透過直接或間接的方式。

以下為與RuntimeError相關的例項,例項中建立了一個類,基類為RuntimeError,用於在

異常觸發時輸出更多的資訊。

在try語句塊中,使用者自定義的異常後執行except塊語句,變數 e 是用於建立Networkerror

類的例項。

    class Networkerror(RuntimeError):

        def __init__(self, arg):

            self.args = arg

    在你定義以上類後,你可以觸發該異常,如下所示:

    try:

        raise Networkerror("Bad hostname")

    except Networkerror,e:

        print e.args

萬能異常

在python的異常中,有一個萬能異常:Exception,它可以捕獲任意異常。

例:

    #cat  aa.py

    s1 = 'hello'

    try:

        int(s1)

    except Exception,e:

        print e

執行結果:  

    #python aa.py 

    invalid literal for int() with base 10: 'hello'

既然有這個萬能異常,其他異常是不是就可以忽略了?當然不是,對於特殊處理或提醒的異常需要先定義,最後定義Exception來確保程式正常執行。

例:

   s1 = 'hello'

    try:

        int(s1)

    except KeyError,e:

        print '鍵錯誤'

    except IndexError,e:

        print '索引錯誤'

    except Exception, e:

        print '錯誤'    

======================================

assert斷言

使用assert斷言是學習python一個非常好的習慣,assert斷言句語格式及用法很簡單。在沒完善一個程式之前,我們不知道程式在哪裡會出錯,與其讓它在執行最崩潰,不如在出現錯誤條件時就崩潰,這時候就需要assert斷言的幫助。

assert斷言的作用

assert斷言是宣告其布林值必須為真的判定,如果發生異常就說明表達示為假。可以理解assert斷言語句為raise-if-not,用來測試表示式,其返回值為假,就會觸發異常。

assert斷言語句的語法格式

assert expression

一些assert用法的語句供參考:

assert 1==1

assert 2+2==2*2

assert len(['my boy',12])<10

assert range(4)==[0,1,2,3]

如何為assert斷言語句新增異常引數

assert的異常引數,其實就是在斷言表示式後新增字串資訊,用來解釋斷言並更好的知道是哪裡出了問題。格式如下:

assert expression [, arguments]

何時使用斷言

Python的assert是用來檢查一個條件,如果它為真,就不做任何事。如果它為假,則會丟擲AssertError並且包含錯誤資訊。例如:

py> x = 23

py> assert x > 0, "x is not zero or negative"

py> assert x%2 == 0, "x is not an even number"

Traceback (most recent call last):

File "", line 1, in ....

AssertionError: x is not an even number

很多人用assert作為一個很快和容易的方法來在引數錯誤的時候丟擲異常。但這樣做是錯的,非常錯誤,有兩個原因。首先AssertError不是在測試引數時應該丟擲的錯誤。你不應該像這樣寫程式碼:

if

not isinstance(x, int):

raise AssertionError("not an int")

你應該丟擲TypeError的錯誤,assert會丟擲錯誤的異常。

但是,更危險的是,有一個關於assert的困擾:它可以被編譯好然後從來不執行,如果你用 –O 或 –oo 選項執行Python,結果不保證assert表示式會執行到。當適當的使用assert時,這是未來,但是當assert不恰當的使用時,它會讓程式碼用-O執行時出錯。

那什麼時候應該使用assert?沒有特定的規則,斷言應該用於:

    防禦型的程式設計

    執行時檢查程式邏輯

    檢查約定

    程式常量

    檢查文件

(在測試程式碼的時候使用斷言也是可接受的,是一種很方便的單元測試方法,你接受這些測試在用-O標誌執行時不會做任何事。我有時在程式碼裡使用assert False來標記沒有寫完的程式碼分支,我希望這些程式碼執行失敗。儘管丟擲NotImplementedError可能會更好。)

關於斷言的意見有很多,因為它能確保程式碼的正確性。如果你確定程式碼是正確的,那麼就沒有用斷言的必要了,因為他們從來不會執行失敗,你可以直接移除這些斷言。如果你確定檢查會失敗,那麼如果你不用斷言,程式碼就會透過編譯並忽略你的檢查。

在以上兩種情況下會很有意思,當你比較肯定程式碼但是不是絕對肯定時。可能你會錯過一些非常古怪的情況。在這個情況下,額外的執行時檢查能幫你確保任何錯誤都會盡早地被捕捉到。

另一個好的使用斷言的方式是檢查程式的不變數。一個不變數是一些你需要依賴它為真的情況,除非一個bug導致它為假。如果有bug,最好能夠儘早發現,所以我們為它進行一個測試,但是又不想減慢程式碼執行速度。所以就用斷言,因為它能在開發時開啟,在產品階段關閉。

一個非變數的例子可能是,如果你的函式希望在它開始時有資料庫的連線,並且承諾在它返回的時候仍然保持連線,這就是函式的不變數:

def

some_function(arg):

    assert

not  DB.closed()

    ...

# code goes here

    assert

not  DB.closed()

    return

result

斷言本身就是很好的註釋,勝過你直接寫註釋:

# when we reach here, we know that n > 2

你可以透過新增斷言來確保它:

assert n > 2

斷言也是一種防禦型程式設計。你不是讓你的程式碼防禦現在的錯誤,而是防止在程式碼修改後引發的錯誤。理想情況下,單元測試可以完成這樣的工作,可是需要面對的現實是,它們通常是沒有完成的。人們可能在提交程式碼前會忘了執行測試程式碼。有一個內部檢查是另一個阻擋錯誤的防線,尤其是那些不明顯的錯誤,卻導致了程式碼出問題並且返回錯誤的結果。

加入你有一些if…elif 的語句塊,你知道在這之前一些需要有一些值:

# target is

expected to be one of x, y, or z, and nothing else.

if

target == x:

    run_x_code()

elif target == y:

    run_y_code()

else:

    run_z_code()

假設程式碼現在是完全正確的。但它會一直是正確的嗎?依賴的修改,程式碼的修改。如果依賴修改成 target = w 會發生什麼,會關係到run_w_code函式嗎?如果我們改變了程式碼,但沒有修改這裡的程式碼,可能會導致錯誤的呼叫 run_z_code 函式並引發錯誤。用防禦型的方法來寫程式碼會很好,它能讓程式碼執行正確,或者立馬執行錯誤,即使你在未來對它進行了修改。

在程式碼開頭的註釋很好的一步,但是人們經常懶得讀或者更新註釋。一旦發生這種情況,註釋會變得沒用。但有了斷言,我可以同時對程式碼塊的假設書寫文件,並且在它們違反的時候觸發一個乾淨的錯誤

assert target in

(x, y, z)

if

target == x:

    run_x_code()

elif target == y:

    run_y_code()

else:

    assert target == z

    run_z_code()

這樣,斷言是一種防禦型程式設計,同時也是一種文件。我想到一個更好的方案:

if

target == x:

    run_x_code()

elif target == y:

    run_y_code()

elif target == z:

    run_z_code()

else:

    # This can never happen. But just in

case  it does...

    raise RuntimeError("an unexpected error occurred")

按約定進行設計是斷言的另一個好的用途。我們想象函式與呼叫者之間有個約定,比如下面的:

“如果你傳給我一個非空字串,我保證傳會字串的第一個字母並將其大寫。”

如果約定被函式或呼叫這破壞,程式碼就會出問題。我們說函式有一些前置條件和後置條件,所以函式就會這麼寫:

def first_upper(astring):

    assert isinstance(astring, str) and len(astring) > 0

    result = astring[0].upper()

    assert isinstance(result, str) and len(result) == 1

    assert result == result.upper()

    return

result

按約定設計的目標是為了正確的程式設計,前置條件和後置條件是需要保持的。這是斷言的典型應用場景,因為一旦我們釋出了沒有問題的程式碼到產品中,程式會是正確的,並且我們能安全的移除檢查。

建議不要用斷言的場景:

不要用它測試使用者提供的資料

不要用斷言來檢查你覺得在你的程式的常規使用時會出錯的地方。斷言是用來檢查非常罕見的問題。你的使用者不應該看到任何斷言錯誤,如果他們看到了,這是一個bug,修復它。

有的情況下,不用斷言是因為它比精確的檢查要短,它不應該是懶碼農的偷懶方式。

不要用它來檢查對公共庫的輸入引數,因為它不能控制呼叫者,所以不能保證呼叫者會不會打破雙方的約定。

不要為你覺得可以恢復的錯誤用斷言。換句話說,不用改在產品程式碼裡捕捉到斷言錯誤。

不要用太多斷言以至於讓程式碼很晦澀。

©著作權歸作者所有:來自51CTO部落格作者CrazyWing的原創作品,如需轉載,請註明出處,否則將追究法律責任

閆強python異常 Python開發實戰


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4548/viewspace-2821823/,如需轉載,請註明出處,否則將追究法律責任。

相關文章