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

大牧莫邪發表於2017-05-24

本節內容如下: 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直接包含並處理所有異常

  1. 使用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巢狀處理指定的多個異常

  2. 使用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程式碼塊

  3. 使用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    使用者程式碼生成的警告

相關文章