Python 簡明教程 --- 23,Python 異常處理
阿新 • • 發佈:2020-07-17
> **微信公眾號:碼農充電站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