1. 程式人生 > >Python 簡明教程 --- 23,Python 異常處理

Python 簡明教程 --- 23,Python 異常處理

> **微信公眾號:碼農充電站pro** > **個人主頁:** > **要麼做第一個,要麼做最好的一個。** **目錄** ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200717174828813.png#pic_center) 我們在編寫程式時,總會不自覺的出現一些錯誤,比如`邏輯錯誤`,`語法錯誤`和一些其它的`執行時錯誤`等。 - **邏輯錯誤:** 這種錯誤不會導致程式`崩潰`,它不容易被發現,只有在執行結果不是我們預期的時候,才會被發現。 - **語法錯誤:** 這種錯誤是不符合語法規定的錯誤,說白了,就是`編譯器`或者`直譯器`無法理解的程式碼。出現這種錯誤時,程式是不能執行的。 - **其它執行時錯誤:** 這種錯誤是程式在執行的過程中出現的,一般情況下不會出現,但是極端情況下會出現,是程式編寫者考慮不夠周全導致的。 在寫程式時一定要把所有的情況都考慮到,並且處理掉,不能有僥倖心理(認為某種情況不會出現)。在程式中,只要是`有可能`出現的情況,那就`一定`會出現。 程式設計師也喜歡將這些錯誤戲稱為`bug`,`bug`代表軟體系統中的`漏洞`或`缺陷`,`bug`需要修正,否則程式是無法正常執行的。重要的軟體系統如果出現漏洞,會帶來巨大的危害。因此,在軟體初步完成後,要進行嚴格的,全面的測試,否則將漏洞百出。 Python 中提供了一套處理錯誤的機制,叫做`異常`。 比較普通的處理錯誤的方法,是使用`if 語句` 或`斷言assert`來對各種情況進行判斷,從而進行相應的處理。而`異常`是一種更加高階的處理錯誤的機制。 ### 1,常見異常 我們來看一些Python 中常見的異常。 **`SyntaxError` 異常** Python 語言有自己的語法格式和規則,如果我們沒有遵守這些規則,將會出現異常: ```shell >
>> print('abc')- # 右括號後邊有一個橫線 File "", line 1 print('abc')- ^ SyntaxError: invalid syntax ``` 上尖括號`^`指出了異常出現的的位置。 **`NameError` 異常** 如果我們使用了一個`未定義`的變數,將會出現該異常: ```shell >>> print(a) # a 變數未定義 Traceback (most recent call last): File "", line 1, in NameError: name 'a' is not defined ``` **`ZeroDivisionError` 異常** 進行除法運算時,如果除數為`0`,將會該異常: ```shell >>> 1 / 0 # 除數為 0 Traceback (most recent call last): File "", line 1, in ZeroDivisionError: division by zero ``` 如果程式沒有處理異常,在異常出現時,將會崩潰退出,Python 直譯器會為你定位到`異常`出現的位置,有助於快速的解決異常。 ### 2,處理異常 異常需要捕獲,從而處理異常。如果在發生異常時,這個異常沒有被捕獲處理,這個異常將會一層一層的向上拋,直到這個異常被捕獲處理,或者程式崩潰。 **`try except` 語句** 在Python 中使用`try` 語句塊來捕獲異常,在`except` 語句塊中處理異常。 我們一般將`有可能出現異常`的語句放在`try` 語句塊中,在`except` 語句塊中編寫處理異常的措施。 比如,我們有如下程式碼: ```python def hello(s): print('hello %s' % s) hello('123', 'abc') ``` 其中`hello` 函式的定義是需要接收一個引數,而在呼叫時,傳遞了兩個引數,執行此程式碼將出現如下異常: ```shell Traceback (most recent call last): File "Test.py", line 8, in hello('123', 'abc') TypeError: hello() takes 1 positional argument but 2 were given ``` 程式碼在遇到`異常`時,會在異常程式碼處丟擲異常,後邊的程式碼將不會再執行。如果異常程式碼沒有處理,程式將崩潰退出。 我們可以這樣處理該異常: ```python try: hello('123', 'abc') except Exception as e: print(e) ``` 我們將`呼叫語句`放在了`try` 語句塊中,這樣就可以捕獲異常。`except` 關鍵字的後邊是要捕獲的`異常的名字`,`as` 後邊是捕獲到的異常 `e`。在`except` 語句塊中,我們只是將捕獲到的異常列印了出來,執行該程式碼,結果如下: ```shell hello() takes 1 positional argument but 2 were given ``` 可見錯誤被列印了出來。我們還可以在列印錯誤之後,再正確的呼叫`hello` 函式: ```python try: hello('123', 'abc') except Exception as e: print(e) hello('123') ``` 執行結果如下: ```shell hello() takes 1 positional argument but 2 were given hello 123 ``` 可見,執行到`hello('123', 'abc')` 時,出現異常,然後程式碼執行到`except` 語句塊,`print(e)` 將錯誤打印出來,又執行了`hello('123')`。 > 列印異常是最簡單的處理異常的方式,在工作中,我們會將異常資訊記錄在日誌檔案中,這樣可以將異常記錄下來,以便處理異常。 一般在捕獲異常時,儘量只在`try` 語句塊中編寫有可能發生異常的程式碼,基本不會發生異常的語句不要寫在`try` 塊中,這樣可以減少`try` 塊中的程式碼量,有助於定位問題。 **`except` 語句** `except` 關鍵字後邊可以跟一個`異常名字`,也可以跟一組`異常名字`,一組異常時,將多個異常的名字寫在一個元組中,語法如下: ```python except (Error1, Error2...): pass ``` 一個`try` 語句中,也可以包含多個`except` 語句塊,語法如下: ```python try: # 程式碼塊 except Error1 as e: # 處理 Error1 except Error2 as e: # 處理 Error2 . . . except: e = sys.exc_info()[0]) pass ``` 多個`except` 語句塊時,Python 直譯器會從上到下依次判斷異常的型別,直到符合某個異常時,會執行對應的語句塊中的程式碼,在該`except` 塊之後的`except` 塊將被忽略。 其中,最後一個`except` 塊可以省略異常的名字,這種格式可以匹配任意的異常,在該塊中,可以使用`sys.exc_info()[0]` 來獲取異常。 在有多個`except` 語句塊時,要注意,前邊的異常的範圍應該小於等於後邊的異常的範圍,否則,後邊的`except` 塊將沒有意義。 **`else` 語句** Python 中,`try... except...` 之後還可以有一個`else`語句塊,`except` 語句塊是在遇到異常時執行的,`else` 語句塊是在沒有遇到異常時執行的。 發生異常時,示例: ```python try: hello('123', 'abc') except Exception as e: print('發生異常') print(e) else: print('沒有發生異常') ``` 上面的程式碼中,執行到`hello('123', 'abc')` 時會發生異常,然後會進入到`except` 語句塊,`else` 語句不會被執行,執行結果如下: ```shell 發生異常 hello() takes 1 positional argument but 2 were given ``` 沒有發生異常時,示例: ```python try: hello('123') except Exception as e: print('發生異常') print(e) else: print('沒有發生異常') ``` 上面程式碼中的`try` 語句塊不會發生異常,那麼`except` 語句就不會執行,`else` 語句會執行,結果如下: ```shell hello 123 沒有發生異常 ``` > **注意:** > > `else` 語句的使用頻率並不高。 **`finally` 語句** `finally` 語句塊無論是否異常都會被執行,該語句塊經常用在需要關閉系統資源的情況下。 沒有發生異常時,示例如下: ```python try: hello('123') except Exception as e: print('發生異常') print(e) else: print('沒有發生異常') finally: print('執行了 finally 語句塊') ``` 上面程式碼中,`try` 語句塊沒有發生異常,`else` 與 `finally` 都被執行,`except` 語句塊沒有執行。執行結果如下: ```shell hello 123 沒有發生異常 執行了 finally 語句塊 ``` 發生異常時,示例如下: ```python try: hello('123', 'abc') except Exception as e: print('發生異常') print(e) else: print('沒有發生異常') finally: print('執行了 finally 語句塊') ``` 上面程式碼中,`try` 語句塊發生異常,`except` 與 `finally` 都被執行,`else` 語句塊沒有執行。執行結果如下: ```shell 發生異常 hello() takes 1 positional argument but 2 were given 執行了 finally 語句塊 ``` > **注意:** > > `else` 語句塊與`finally` 語句塊可以同時存在,也可以同時不存在,也可以一個存在一個不存在,互不影響。 ### 3,丟擲異常 **`raise` 異常** 如果你捕獲了一個異常,卻不想徹底解決這個異常,而想將該異常向上層丟擲,可以使用`raise` 關鍵字。 `raise` 用於丟擲異常,其後可以跟一個`異常物件`,或者什麼也不跟。 `raise` 後跟一個`異常物件`: ```python raise Exception('這裡發生了錯誤') ``` `raise` 後什麼也不跟: ```python try: hello('123', 'abc') except Exception as e: print('發生異常') raise ``` Python 直譯器會記錄最後一個發生的異常,`raise` 會將最後一個異常丟擲。上面程式碼中的`raise` 相當於`raise e`。 **`assert` 斷言** `assert` 語句稱為`斷言`,就是判斷某個`表示式`是否為真: - 表示式為`True` 時,正常通過 - 表示式為`False` 時,丟擲`AssertionError` 異常 示例如下: 表示式`1 == 1`為`True`,沒有反應: ```shell >>> assert 1 == 1 ``` 表示式`1 == 0`為`False`,丟擲異常: ```shell >>> assert 1 == 0 Traceback (most recent call last): File "", line 1, in AssertionError ``` ### 4,Python 異常層次結構 Python 異常層次結構如下: ```python BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StopAsyncIteration +-- ArithmeticError | +-- FloatingPointError | +-- OverflowError | +-- ZeroDivisionError +-- AssertionError +-- AttributeError +-- BufferError +-- EOFError +-- ImportError | +-- ModuleNotFoundError +-- LookupError | +-- IndexError | +-- KeyError +-- MemoryError +-- NameError | +-- UnboundLocalError +-- OSError | +-- BlockingIOError | +-- ChildProcessError | +-- ConnectionError | | +-- BrokenPipeError | | +-- ConnectionAbortedError | | +-- ConnectionRefusedError | | +-- ConnectionResetError | +-- FileExistsError | +-- FileNotFoundError | +-- InterruptedError | +-- IsADirectoryError | +-- NotADirectoryError | +-- PermissionError | +-- ProcessLookupError | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError +-- SystemError +-- TypeError +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning +-- ResourceWarning ``` > 可參考這裡: > > `https://docs.python.org/3/library/exceptions.html#exception-hierarchy` 這些都是`內建異常`,`BaseException` 是所有異常的父類,我們使用最多的是`Exception` 及其子類。可以使用`help(類名)`來檢視每個類的詳情。 ### 5,自定義異常 有時候,我們需要定義自己的`異常類`,來滿足自己的需求。 我們已經知道,Python 異常類有自己的層次結構,所有的類都直接或者間接繼承了`BaseException`。因此,使用者自定義的異常類,也需要滿足這種層次結構。 一般情況下,自定義異常需要繼承`Exception` 類。如下: ```python class MyError(Exception): pass ``` `MyError` 類的使用方式,跟內建異常類的使用方式一樣。你可以根據自己的需要,為`MyError` 類編寫相應的構造方法,和其它類方法。 如果沒有為`MyError` 編寫構造方法,那麼`MyError` 就繼承了`Exception` 的構造方法。 ### 6,除錯錯誤 程式編寫完成後不一定是正確的,當發現有錯誤時,就需要定位錯誤的位置。 最普遍,最簡單的調錯的方法就是列印某個變數,通過輸出變數的值,來檢視其是否是你想要的結果。 另一種比較高效,有力的除錯程式碼的方式是`單步除錯`,即是通過設定`斷點`,深入到程式碼內部,一步一步的跟蹤檢視程式碼的執行結果是否正確,從而達到修正程式碼的目的。 在`C 語言`中有一個非常著名的工具叫做`gdb`,這是一款強大的除錯工具。Python 中也有類似的一款工具叫做`pdb`,它使用起來要比`gdb` 簡單許多。 在Python 中,`pdb` 是一個模組,所以,在使用之前要先使用`import pdb` 將該模組引入。然後,在需要除錯程式碼的地方,使用`pdb.set_trace()` 方法來設定斷點,在程式碼執行到此處時,Python 直譯器就會從此處開始讓你調式程式碼。 如下程式碼,檔名為`Test.py`: ```python #! /usr/bin/env python3 # 引入 pdb 模組 import pdb def hello(s): print('hello %s' % s) # 設定斷點 pdb.set_trace() hello('python') hello('java') ``` 我們使用`python3` 來執行該程式,如下: ```shell $ python3 Test.py > ~/Test.py(12)() -> hello('python') (Pdb) ``` 可以看到程式碼在`hello('python')`之前暫停並進入斷點,控制檯顯示出`(Pdb)`,我們可以在這個後面輸入Python 程式碼或者`pdb` 支援的命令。 `pdb` 常用命令如下: - `n`:進行下一步程式碼,即單步執行 - `c`:程式碼執行到下一個斷點處,如果沒有下一個斷點,則執行到程式結束 - `s`:在遇到函式時,使用`s` 命令,可以進入函式內部 - `l`:列出當前語句周圍的10行程式碼 - `p`:用於輸出變數的值,相當於`print` 函式 (完。) **推薦閱讀:** [Python 簡明教程 --- 18,Python 面向物件](https://www.cnblogs.com/codeshell/p/13193851.html) [Python 簡明教程 --- 19,Python 類與物件](https://www.cnblogs.com/codeshell/p/13193866.html) [Python 簡明教程 --- 20,Python 類中的屬性與方法](https://www.cnblogs.com/codeshell/p/13197968.html) [Python 簡明教程 --- 21,Python 繼承與多型](https://www.cnblogs.com/codeshell/p/13233821.html) [Python 簡明教程 --- 22,Python 閉包與裝飾器](https://blog.csdn.net/LUAOHAN/article/details/107123986) --- 歡迎關注作者公眾號,獲取更多技術乾貨。 ![碼農充電站pro](https://img-blog.csdnimg.cn/20200505082843773.png?#pic