天天看點

python錯誤處理 一

先看一個例子:

>>> def first_example(n):
...     return  / n
...
>>> first_example()

>>> first_example()
Traceback (most recent call last):
  File "<stdin>", line , in <module>
  File "<stdin>", line , in first_example
ZeroDivisionError: division by zero
>>>
從例子中得知,n不能為,否則将會報一個ZeroDivisionError
(除數不能為)的錯誤,我們需要對錯誤進行處理,比如說當發生
的時候我們可以事先約定一個錯誤代碼,這樣當以後出錯的時候就可
出錯的原因。而上面的ZeroDivisionError就是系統内定的一個
錯誤代碼。
           

通常在python中我們使用,以下格式來定義一個錯誤代碼:

>>> def first_example(n):
...     try:
...             return  / n
...     except ZeroDivisionError as error:
...             print('錯誤資訊:',error)
...
>>> first_example()

>>> first_example()
錯誤資訊: division by zero
>>>
将你需要執行的代碼塊(該代碼塊運作時可能出錯)放
在try語句裡面,将出錯類型放在except後面,并給其
賦一個引用變量,下面的print是列印錯誤資訊,前半
部分是自定義字元串。
           

看第二個例子,函數之間的 錯誤處理:

>>> def too(n):
...     return  / n
...
>>> def foo(n):
...     return too(n)
...
>>> def second_example(n):
...     try:
...             print(foo(n))
...     except ZeroDivisionError as a:
...             print('出錯原因:',a)
...
>>> second_example()

>>> second_example()
出錯原因: division by zero
>>>
           

我們繼續往下看,我們在上面的例子上進行修改:

>>> def too(n):
...     return  / n
...
>>> def foo(n):
...     return too(n) * 
...
>>> def second_example(n):
...     try:
...             print(foo(n))
...     except ZeroDivisionError as a:
...             print('出錯原因:',a)
...     else:
...             print('看看我的出現規則')
...
>>>
>>> second_example()

看看我的出現規則
>>> second_example()
出錯原因: division by zero
不難看出,else語句塊是在沒有出錯的時候執行
出錯的時候就不再執行
           

我們再進行修改一下 :

>>> def too(n):
...     return  / n
...
>>> def foo(n):
...     return too(n) * 
...
>>> def second_example(n):
...     try:
...             print(foo(n))
...     except ZeroDivisionError as a:
...             print('出錯原因:',a)
...     finally:
...             print('我是finally代碼塊')
...
>>> second_example()

我是finally代碼塊
>>> second_example()
出錯原因: division by zero
我是finally代碼塊

可以看出,不管代碼有沒有出錯,finally代碼塊都會執行
           

我們再增加一點東西:

>>> def too(n):
...     return int(n)
...
>>> def foo(s):
...     a = s.split('+')
...     b = map(too,a)
...     return reduce(lambda x, y: x + y , b)
>>> def main(q,w):
...     try :
...             print( / q)
...             print(w + '= ',foo(w))
...     except ZeroDivisionError as error:
...             print('錯誤資訊:',error)
...     except ValueError as error:
...             print('錯誤資訊:',error)
...     finally:
...             print('代碼執行完畢')
...
>>> main(,'100 + 23 + 45')

 +  + =  
代碼執行完畢
>>> main(,'100 + 23 + 45')
錯誤資訊: division by zero
代碼執行完畢
>>> main(,'100 + 23 + 4.5')

錯誤資訊: invalid literal for int() with base : ' 4.5'
代碼執行完畢
>>> main(,'100 + 23 + 4.5')
錯誤資訊: division by zero
代碼執行完畢
我們還是将需要執行但可能出錯的代碼放在try
語句中,但我們有兩個except,因為我們會猜測
需要執行的代碼可能不止報一個錯誤。是以我們可
以寫多個except
           

python 錯誤也是class,所有的錯誤類型都繼承自

BaseException,是以在寫except的時候我們應該

注意,如果有多個except,父類錯誤類型在前面的

except中,子類錯誤類型在後面的except中,那當

出現子類錯誤類型 的時候,報的是父類錯誤類型,

子類錯誤類型永遠不會有執行的機會,舉個例子:

>>> def third_example(n):
...     try:
...             return  / n
...     except BaseException as a:
...             print('BaseException',a)
...     except ZeroDivisionError as error:
...             print('ZeroDivisionError',error)
...
>>> third_example()
BaseException division by zero
是以為了避免這種情況,我們應該把表示範
圍小的子類錯誤類型放前面。
           

常見的錯誤類型和繼承關系進入這個連結:

常見錯誤類型和繼承關系

錯誤處理的方式還有一種叫斷言(assert):

>>> def odd(n):
...     assert n
...     return  / n
...
>>> def fourth_example(n):
...     print(odd(n))
...
>>> fourth_example()

>>> fourth_example()
Traceback (most recent call last):
  File "<stdin>", line , in <module>
  File "<stdin>", line , in fourth_example
  File "<stdin>", line , in odd
AssertionError
>>>
斷言的文法是,當斷言後面的語句為真時,繼續往下執行,
當為假時就會報一個AssertionError的錯誤提示
           

我們再來看一下assert的更新版本:

>>> def odd(n):
...     if n == :
...             raise ValueError('0不能做除數')
...     return  / n
...
>>> def fourth_example(n):
...     print(odd(n))
>>> fourth_example()

>>> fourth_example()
Traceback (most recent call last):
  File "<stdin>", line , in <module>
  File "<stdin>", line , in fourth_example
  File "<stdin>", line , in odd
ValueError: 不能做除數
我們使用了raise關鍵字,當判斷除數為時
我們就會執行raise所在的語句,
我們在增加點佐料:
def odd(n):
        if n ==  :
                raise ZeroDivisionError('0不能做除數')
        return   / n
def fourth_example(n):
        try:
                print(odd(n))
        except ZeroDivisionError as a:
                print('錯誤提示:',a)
fourth_example(int(input('請輸入一個數字:')))
請輸入一個數字:

請輸入一個數字:
錯誤提示: 不能做除數
我的了解是 raise可以改變錯誤類型和後面的錯誤提示的内容
不加raise時的提示資訊是:
請輸入一個數字:
錯誤提示: division by zero
我們再修改一下
def odd(n):
        if n ==  :
                raise ZeroDivisionError('0不能做除數')
        return   / n
def fourth_example(n):
        try:
                print(odd(n))
        except ValueError as a:
                print('錯誤提示:',a)

fourth_example(int(input('請輸入一個數字:')))

請輸入一個數字:
Traceback (most recent call last):
  File "G:/Pyproject/pachong/pachong.py", line , in <module>
    fourth_example(int(input('請輸入一個數字:')))
  File "G:/Pyproject/pachong/pachong.py", line , in fourth_example
    print(odd(n))
  File "G:/Pyproject/pachong/pachong.py", line , in odd
    raise ZeroDivisionError('0不能做除數')
ZeroDivisionError: 不能做除數
結果并沒有except裡面的語句,隻有raise
           

在工作中我們有時候會這麼做:

def foo(s):
    n = int(s)
    if n==:
        raise ValueError('invalid value: %s' % s)
    return  / n

def bar():
    try:
        foo('0')
    except ValueError as e:
        print('ValueError!')
        raise

bar()
運作一下:
Traceback (most recent call last):
ValueError!
  File "G:/Pyproject/pachong/pachong.py", line , in <module>
    bar()
  File "G:/Pyproject/pachong/pachong.py", line , in bar
    foo('0')
  File "G:/Pyproject/pachong/pachong.py", line , in foo
    raise ValueError('invalid value: %s' % s)
ValueError: invalid value: 

我們會捕獲錯誤在抛出錯誤,捕獲錯誤目的隻是記錄一下,
便于後續追蹤。但是,由于目前函數不知道應該怎麼處理該錯誤,
是以,最恰當的方式是繼續往上抛,讓頂層調用者去處理。好比
一個員工處理不了一個問題時,就把問題抛給他的老闆,如果他
的老闆也處理不了,就一直往上抛,最終會抛給CEO去處理。
           

因為錯誤是class,捕獲一個錯誤就是捕獲到該class的

一個執行個體。是以,錯誤并不是憑空産生的,而是有意創

建并抛出的。Python的内置函數會抛出很多類型的錯誤

,我們自己編寫的函數也可以抛出錯誤。

如果要抛出錯誤,首先根據需要,可以定義一個錯誤的

class,選擇好繼承關系,然後,用raise語句抛出一個錯

誤的執行個體:

class FooError(ValueError):
    pass

def foo(s):
    n = int(s)
    if n==:
        raise FooError('invalid value: %s' % s)
    return  / n

foo('0')
執行一下:
Traceback (most recent call last):
  File "err_throw.py", line , in <module>
    foo('0')
  File "err_throw.py", line , in foo
    raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 
           

最後我們在說一下記錄錯誤,Python内置的logging

子產品可以非常容易地記錄錯誤資訊:

import logging

def foo(s):
    return  / int(s)

def bar(s):
    return foo(s) * 

def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)

main()
print('END')
執行一下:
ERROR:root:division by zero
Traceback (most recent call last):
  File "err_logging.py", line , in main
    bar('0')
  File "err_logging.py", line , in bar
    return foo(s) * 
  File "err_logging.py", line , in foo
    return  / int(s)
ZeroDivisionError: division by zero
END
你會發現,錯誤後面的語句也可以執行。
通過配置,logging還可以把錯誤記錄到日志
檔案裡,友善事後排查,這些我們以後都會接觸。