CrazyWing:Python自動化運維開發實戰 十7、Python異常

導語:

在寫代碼的時候,常常會遇到異常。
python提供了兩個功能來處理程序在運行中出現的異常和錯誤,可使用該功能來調試python程序。 python

  1. 異常處理
  2. 斷言(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自動化運維開發實戰 十7、Python異常
CrazyWing:Python自動化運維開發實戰 十7、Python異常
CrazyWing:Python自動化運維開發實戰 十7、Python異常

什麼是異常?

異常便是一個事件,該事件會在程序執行過程當中發生,影響程序的正常執行。
通常狀況下,在Python沒法正常處理程序時就會發生一個異常,異常是Python對象,表示一
個錯誤。 數據庫

當Python腳本發生異常時咱們須要捕獲處理它,不然程序會終止執行。 express

#!/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,以下實例:app

try:
        正常的操做
        ......................
     except:
        發生異常,執行這塊代碼
        ......................
     else:
        若是沒有異常執行這塊代碼

以上方式try-except語句捕獲全部發生的異常。但這不是一個很好的方式,咱們不能經過該程序
識別出具體的異常信息。由於它捕獲全部的異常。 運維

使用except帶多種異常類型

也可使用相同的except語句來處理多個異常信息: ide

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

按約定設計的目標是爲了正確的編程,前置條件和後置條件是須要保持的。這是斷言的典型應用場景,由於一旦咱們發佈了沒有問題的代碼到產品中,程序會是正確的,而且咱們能安全的移除檢查。

建議不要用斷言的場景:

  1. 不要用它測試用戶提供的數據
  2. 不要用斷言來檢查你以爲在你的程序的常規使用時會出錯的地方。斷言是用來檢查很是罕見的問題。你的用戶不該該看到任何斷言錯誤,若是他們看到了,這是一個bug,修復它。
  3. 有的狀況下,不用斷言是由於它比精確的檢查要短,它不該該是懶碼農的偷懶方式。
  4. 不要用它來檢查對公共庫的輸入參數,由於它不能控制調用者,因此不能保證調用者會不會打破雙方的約定。
  5. 不要爲你以爲能夠恢復的錯誤用斷言。換句話說,不用改在產品代碼裏捕捉到斷言錯誤。
  6. 不要用太多斷言以致於讓代碼很晦澀。
相關文章
相關標籤/搜索