28. 企業級開發基礎9:異常處理

本節內容如下:

  1. 什么是異常,對異常的解釋和描述,口語描述和專業術語的聯系
  • 代碼中出現錯誤的處理手段
  • 異常處理方式
    • 什么樣的情況算異常
    • 捕獲異常【try-except-else-finally】
    • 拋出異常【raise】
  • 常見異常

1. 什么是異常

我們程序在開發過程中,總會遇到各種各樣的一些問題,有些是由于拼寫、配置、選項等等各種引起的程序錯誤,有些是由于程序功能處理邏輯不完善引起的漏洞,這些統稱為我們程序中的異常

所謂異常:就是不正常的情況,錯誤和漏洞都是不正常的情況,異常情況有時候也會稱呼為BUG,也就是缺陷、漏洞的意思,程序執行過程中出現異常會影響程序的正常執行。

python中內置了一整套完善的異常處理機制,可以讓開發人員快速針對出現問題的代碼進行完善和處理。

我們針對python可能遇到的不同的異常情況,一般會做如下處理:

  • 如果是拼寫、配置等等引起的錯誤,根據出錯信息進行排查錯誤出現的位置進行解決
  • 如果是程序設計不完善引起的漏洞,根據漏洞的情況進行設計處理漏洞的邏輯;
    切記:合理的處理BUG也是程序設計開發的一部分

2. 錯誤處理

錯誤的出現,在程序中一般會有兩種表現,一種是拼寫錯誤,一種是程序執行過程中出現的錯誤,這樣兩種不同的錯誤應該怎么進行追蹤和處理呢?

2.1. 拼寫錯誤

常規情況下,拼寫錯誤只是在簡單的記事本等環境下進行開發時,容易手誤產生拼寫錯誤;當前開發環境下,我們經常使用一些半自動化的IDE開發工具,如pycharm等等,可以進行簡單的程序關鍵字的拼寫檢查以及程序結構的檢查,把一些簡單的拼寫問題掐死在萌芽之中

程序設計開發的學習需要經歷一個過程,建議開始的基礎部分使用超級記事本進行開發,如editplus、ultraedit、sublime等等,對于基礎的掌握會有一個非常不錯的提升作用;進入后續的企業級項目開發階段之后可以使用高級開發工具來提升我們的開發效率,如Pycharm、eclipse等等。

2.2. 程序運行時錯誤

程序運行過程中,也會出現各種各樣的錯誤,對于錯誤的出現和提示信息必須有一個比較明確的掌握,才能在后續的程序開發中快速的開發并且修復問題,這里就會出現兩個步驟

  • 確定問題及問題出現的代碼行
  • 后續的問題處理【參考后面的異常處理】

首先我們必須查詢問題出現的錯誤提示信息,觀察如下代碼

# 程序測試
class Person(object):
    # 通過__slots__屬性定義可以擴展的成員屬性名稱
    __slots__ = ("__name", "address", "age", "gender", "email")

    # 初始化方法
    def __init__(self, name):
        self.__names = name

    # 屬性的set/get方法
    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name


# 創建對象
p = Person("tom")
print(p.get_name())

這里我們使用的開發工具是PyCharm,代碼開發過程中,必須時刻觀察我們的編輯工具是否出現錯誤提示,如果出現錯誤提示就是關鍵字拼寫問題或者程序結構設計問題,需要及時修改;上面的代碼開發工具沒有報錯,那就直接運行代碼,出現如下結果:

Traceback (most recent call last):
  File "D:/resp_work/PY_WORK/備課/模塊化開發/demo04/demo10.py", line 18, in <module>
    p = Person("tom")
  File "D:/resp_work/PY_WORK/備課/模塊化開發/demo04/demo10.py", line 7, in __init__
    self.__names = name
AttributeError: 'Person' object has no attribute '_Person__names'

運行結果中出現了錯誤,錯誤的名稱是AttributeError,錯誤的提示是'Person' object has no attribute '_Person__names',簡單翻譯過來就是在Person對象中沒有屬性_Person__names
僅僅依靠這樣的錯誤提示,我們已經了解到,可能是我們對象的屬性操作過程中出現了什么錯誤,到底出現了什么錯誤呢?繼續觀察上面的錯誤代碼:
從錯誤的第一行代碼

Traceback (most recent call last):

這行代碼的意思是跟蹤錯誤的出現的過程,查看跟蹤提示信息下面的第一行錯誤提示:

File "D:/resp_work/PY_WORK/備課/模塊化開發/demo04/demo10.py", line 18, in <module>
    p = Person("tom")

首先在文件D:/resp_work/PY_WORK/備課/模塊化開發/demo04/demo10.py的第8行出現了錯誤,錯誤代碼是p = Person("tom"),這里是錯誤開始的地方,明顯這里的代碼沒有什么錯誤,那就接著往下看

  File "D:/resp_work/PY_WORK/備課/模塊化開發/demo04/demo10.py", line 7, in __init__
    self.__names = name

在文件D:/resp_work/PY_WORK/備課/模塊化開發/demo04/demo10.py的第7行line 7出現的錯誤,主要代碼是self.__names = name,看到這里,我們已經明確,是在我們程序的__init__(self, name)初始化方法中,寫錯了我們的屬性名稱,屬性名稱本意設置的是__name但是錯誤寫成了__names,修改代碼如下

# 程序測試
class Person(object):
    # 通過__slots__屬性定義可以擴展的成員屬性名稱
    __slots__ = ("__name", "address", "age", "gender", "email")

    # 初始化方法
    def __init__(self, name):
        self.__name = name

    # 屬性的set/get方法
    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name


# 創建對象
p = Person("tom")
print(p.get_name())

執行代碼,出現如下結果:

tom

說明錯誤正常處理了。

解決程序中遇到錯誤的核心操作
核心操作其實就是定位錯誤出現的行號,然后根據對代碼執行前后的簡單分析來定位出現錯誤的地方,簡單的錯誤就可以直接修復;當然,某些情況下如果出現運行過程中可能會出現的錯誤,就是程序中的異常了,對于異常的處理,請參考后面的異常處理部分。

3. 異常處理

所謂異常,是程序執行過程中,出現了不正常的情況影響了整個程序的正常執行
所謂處理異常,就是先通過指定的條件捕獲異常,捕獲到異常之后進行后續的處理,以正常的情況提示并處理發生的異常,讓程序正常的執行的過程
python中出現的所有的異常,都是直接或者間接繼承自BaseException這個類的

3.1. 代碼中什么樣的情況是異常?

python提供了一套try-except-finally的異常處理代碼塊,用于針對可能出現問題的代碼進行容錯和處理

異常處理的語法結構如下:

try:
    <正常要執行的代碼語句,執行過程中可能會出現異常>
except <異常名稱>:
    <對應異常的處理代碼>
else:
    <如果沒有異常,執行的后續代碼>
finally:
    <無論是否出現異常,最終都會執行的代碼>

接下來,觀察下面這段代碼的設計和執行過程,你能發現問題出現在哪里嗎?

# 常規情況下的幾行代碼:計算兩個數的加法運算
def add():
    num1 = int(input("請輸入第一個數字:"))
    num2 = int(input("請輸入第二個數字:"))
    num3 = num1 + num2
    print("兩個數字計算的結果是:" +  str(num3))
# 調用函數開始計算
add()
# 執行結果
~請輸入第一個數字:12
~請輸入第二個數字:10
~兩個數字計算的結果是:22

上述功能的程序設計時,已經考慮了諸多的問題,如用戶輸入的數據應該是字符串,代碼中通過int()方法進行了強制類型轉換,在最后輸出數據的時候,由于num3是數值,數值和字符串不能直接用符號+連接,所以對num3又通過str()函數強制轉換成了字符串。正常情況下,程序沒有任何問題。

但是上述程序的缺陷并非正常流程下,而是如果用戶在應該輸入數字的情況下,輸入了字母或者其他的非數字字符,程序就出現錯誤了,這個才是我們要解決的程序的BUG

>>> add()
請輸入第一個數字:ab
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in add
ValueError: invalid literal for int() with base 10: 'ab'

我們對上述函數進行如下改造:

# 常規情況下的幾行代碼:計算兩個數的加法運算
def add():
    try:
        num1 = int(input("請輸入第一個數字:"))
        num2 = int(input("請輸入第二個數字:"))
        num3 = num1 + num2
        print("兩個數字計算的結果是:" +  str(num3))
    except:
        print("您輸入的數值非法,只能輸入整數")
 
# 調用函數開始計算:執行過程如下
>>> add()
請輸入第一個數字:12
請輸入第二個數字:13
兩個數字的和:25
>>> add()
請輸入第一個數字:ab
您輸入了非法的非數字字符

可以看到,上面通過添加try-except這樣的一個代碼塊,完美的解決了我們出現的錯誤,不至于讓錯誤導致程序的崩潰

3.2. 異常處理的方式1——捕獲異常

異常處理,python中是通過try-except語句代碼塊來執行處理的

try-except語句代碼塊處理異常通常有這樣幾種方式

  1. 使用try-except直接包含并處理所有異常

執行代碼如下

try:
n = input("請輸入數字:")
num1 = int(n)
print("您輸入了數字:" + str(num1))
except:
print("出現異常,處理異常")
print("程序繼續執行,代碼執行完成!")

>![使用try-except直接包含并處理所有異常](http://upload-images.jianshu.io/upload_images/5988045-aa0fafc390a2a4f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2. 使用try-except-except-except嵌套處理指定的多個異常
> ```
def add():
    try:
        n = input("請輸入數字:")
        num1 = int(n) # 可能出現異常 ValueError
        print("您輸入的數字是:" + num1) # 可能出現異常TypeError
    except ValueError as e: # 處理指定的ValueError異常
        print("輸入的數據不是數字")
    except TypeError as e: # 處理指定的TypeError異常
        print("整數不能喝數字拼接")
# 調用執行
add()
try-except-except-except嵌套處理指定的多個異常
  1. 使用try-except-except-else處理異常并執行else代碼塊

我們通過將可能出現異常的代碼包含在try語句塊中,如果程序執行正常,就執行后續的代碼,可以將后續的代碼放在else中執行

# 編寫記錄用戶輸入的函數
def add():
    try:
        n = input("請輸入數字:")
        num1 = int(n)
    except:
        print("輸入的數據不是數字")
    else:
        print("您輸入的數字是:" + str(num1))
add()
try-except-except-else處理異常并執行else代碼塊
  1. 使用try-except-except-finally處理異常并在finally中進行后續處理

某些情況下,程序在操作的過程中,需要使用一定的資源,如打開文件讀取或者向文件中寫入數據,一旦操作完成,需要關閉和文件的鏈接釋放資源。
此時的流程就是:打開文件->讀取/寫入數據文件->關閉文件
在讀取/寫入數據到文件時,可能會出現異常,此時的要求時,不論是否出現異常,最后的關閉文件的操作必須執行

# 操作文件的函數
def readFile():
    try:
        # 打開文件
        f = open("d:/test.txt")
        # 寫入內容
        f.write("這是要寫到文件中的內容")
    except:
        print("文件讀寫錯誤")
    finally:
        # 關閉文件
        f.close()
# 執行函數
readFile()
try-except-except-finally處理異常并在finally中進行后續處理
3.2. 異常處理的方式2——拋出異常

某些情況下,我們捕獲到異常信息,如果只是簡單的進行處理,對后續的程序可能會造成一定的困擾,舉一個簡單的操作案例:老板讓員工老李去采購一批辦公用品

老板boss.py,讓員工老李Emp.py,采購一批辦公用品
員工老李去采購辦公用品,結果出現異常情況,店面關門了;此時老李如果將這個異常自行處理了,就沒有結果了。老板那里根本不知道老李發生了什么狀況,最終功能沒有完成的同時老板boss.py模塊也沒有得到任何結果。
結果就是~程序出現了BUG,老李遺憾的離職了..

換一種思路
老板boss.py,讓員工老李Emp.py,采購一批辦公用品
員工老李去采購辦公用品,結果出現異常情況,店面關門了;此時老李將異常信息自行簡單處理了一下,同時拋出異常信息匯報給老板:店面關門~可以做其他準備了;老板接收到老李拋給自己的異常信息,臨時調整計劃;最后功能完成了,老李升職了。


這時候我們必須得明確:異常可以捕獲進行處理,適當的時候異常也需要拋出給調用者處理

請觀察我們之前寫過的如下代碼:

# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 異常的拋出,首先要捕獲到異常,將難以理解的異常
# 轉換成比較容易理解的異常拋出給調用者
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定義一個輸入數字的函數
def add():
    try:
        n = input("請輸入一個數字:")
        num = int(n)
    except:
        # 如果出現異常,將ValueError異常轉換成更加容易理解的異常
        raise ValueError("這里需要一個數字,您輸入了非數字字符")

add()
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 轉換之前拋出的異常
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
請輸入一個數字:a
Traceback (most recent call last):
  File "D:/resp_work/PY_WORK/備課/模塊化開發/demo05/demo02.py", line 8, in <module>
    add()
  File "D:/resp_work/PY_WORK/備課/模塊化開發/demo05/demo02.py", line 4, in add
    num = int(n)
ValueError: invalid literal for int() with base 10: 'a'
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 轉換之后拋出的異常:我們可以看到,這里的異常錯誤信息非常明確了
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Traceback (most recent call last):
  File "D:/resp_work/PY_WORK/備課/模塊化開發/demo05/demo02.py", line 8, in <module>
    add()
  File "D:/resp_work/PY_WORK/備課/模塊化開發/demo05/demo02.py", line 6, in add
    raise ValueError("這里需要一個數字,您輸入了非數字字符")
ValueError: 這里需要一個數字,您輸入了非數字字符

拋出異常有兩種情況,第一種情況,當前代碼中可能存在異常,如果一旦出現異常直接拋出,讓調用者進行后續的處理,第二種情況,當前代碼中可能存在異常,但是出現異常的錯誤提示信息非常不明確,需要轉換成我們定義的另一種異常拋出異常,讓調用者更加明確出現的問題
不論是異常處理,還是拋出異常,核心都是為了更加方便的解決問題!

3.3. 異常處理的方式3——拋出自定義異常

如果系統提供的異常不一定符合我們的需要,如用戶登錄失敗,需要提示一個賬號密碼有誤的異常信息,python中是沒有提供這樣的異常對象的,需要開發人員自定義異常來進行處理

我們從前面的內容中已經知道,所有的異常對象都是直接或者間接繼承自BaseException
所以自定義異常如下:

# 自定義異常
class MyException(BaseException):
    pass
# 函數處理
def add():
    try:
        n = input("請輸入一個數字:")
        num = int(n)
    except ValueError as e:
        # 拋出自定義異常信息
        raise MyError("這里需要一個數字,您輸入了非數字字符%s" % n)

add()

自定義異常,在一定程度上擴展了異常的功能,更加方便我們在程序中進行不同錯誤的不同的處理手段和錯誤提示信息,使用的時候根據實際需要進行處理即可!

4. 常見的異常

BaseException 所有異常的基類
SystemExit 解釋器請求退出
KeyboardInterrupt 用戶中斷執行(通常是輸入^C)
Exception 常規錯誤的基類
StopIteration 迭代器沒有更多的值
GeneratorExit 生成器(generator)發生異常來通知退出
StandardError 所有的內建標準異常的基類
ArithmeticError 所有數值計算錯誤的基類
FloatingPointError 浮點計算錯誤

OverflowError 數值運算超出最大限制
ZeroDivisionError 除(或取模)零 (所有數據類型)
AssertionError 斷言語句失敗
AttributeError 對象沒有這個屬性
EOFError 沒有內建輸入,到達EOF 標記
EnvironmentError 操作系統錯誤的基類
IOError 輸入/輸出操作失敗
OSError 操作系統錯誤
WindowsError 系統調用失敗
ImportError 導入模塊/對象失敗
LookupError 無效數據查詢的基類
IndexError 序列中沒有此索引(index)
KeyError 映射中沒有這個鍵
MemoryError 內存溢出錯誤(對于Python 解釋器不是致命的)
NameError 未聲明/初始化對象 (沒有屬性)
UnboundLocalError 訪問未初始化的本地變量
ReferenceError 弱引用(Weak reference)試圖訪問已經垃圾回收了的對象
RuntimeError 一般的運行時錯誤
NotImplementedError 尚未實現的方法
SyntaxError Python 語法錯誤
IndentationError 縮進錯誤
TabError Tab 和空格混用
SystemError 一般的解釋器系統錯誤
TypeError 對類型無效的操作
ValueError 傳入無效的參數
UnicodeError Unicode 相關的錯誤
UnicodeDecodeError Unicode 解碼時的錯誤
UnicodeEncodeError Unicode 編碼時錯誤
UnicodeTranslateError Unicode 轉換時錯誤
Warning 警告的基類
DeprecationWarning 關于被棄用的特征的警告
FutureWarning 關于構造將來語義會有改變的警告
OverflowWarning 舊的關于自動提升為長整型(long)的警告
PendingDeprecationWarning 關于特性將會被廢棄的警告
RuntimeWarning 可疑的運行時行為(runtime behavior)的警告
SyntaxWarning 可疑的語法的警告
UserWarning 用戶代碼生成的警告


大牧莫邪.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容

  • Python異常處理 異常概念: 異常:就是不正常的情況,程序開發過程中錯誤和BUG都是補充正常的情況 異常發生的...
    youngkun閱讀 930評論 0 4
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,674評論 25 708
  • python使用被稱為異常的特殊對象來管理程序執行期間發生的錯誤。每當發生讓python不知所措的錯誤時,...
    路_堯知百戰勝閱讀 523評論 0 0
  • 邊剔著牙,邊想:牙,是真的不好了。 女兒曾痛心疾首:一口好好的牙,讓自己生生糟蹋壞了。 想想自己的行為...
    莒子閱讀 1,225評論 15 4
  • 營救 紫膺文 我來了很久了,依然不知這里是什么地方。周圍三三兩兩地坐著、躺著一些人,他們也不說什么,不做什么,只是...
    紫膺閱讀 320評論 0 0