我與python約個會:28. 企業級開發基礎9:異常處理

本節內容如下:

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

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直接包含並處理所有異常
  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嵌套處理指定的多個異常
  3. 使用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代碼塊
  4. 使用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 用戶代碼生成的警告

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章