微信公眾號:碼農充電站pro
個人主頁:https://codeshellme.github.io
要麼做第一個,要麼做最好的一個。
目錄
我們在編寫程式時,總會不自覺的出現一些錯誤,比如邏輯錯誤
,語法錯誤
和一些其它的執行時錯誤
等。
- 邏輯錯誤: 這種錯誤不會導致程式
崩潰
,它不容易被發現,只有在執行結果不是我們預期的時候,才會被發現。 - 語法錯誤: 這種錯誤是不符合語法規定的錯誤,說白了,就是
編譯器
或者直譯器
無法理解的程式碼。出現這種錯誤時,程式是不能執行的。 - 其它執行時錯誤: 這種錯誤是程式在執行的過程中出現的,一般情況下不會出現,但是極端情況下會出現,是程式編寫者考慮不夠周全導致的。
在寫程式時一定要把所有的情況都考慮到,並且處理掉,不能有僥倖心理(認為某種情況不會出現)。在程式中,只要是有可能
出現的情況,那就一定
會出現。
程式設計師也喜歡將這些錯誤戲稱為bug
,bug
代表軟體系統中的漏洞
或缺陷
,bug
需要修正,否則程式是無法正常執行的。重要的軟體系統如果出現漏洞,會帶來巨大的危害。因此,在軟體初步完成後,要進行嚴格的,全面的測試,否則將漏洞百出。
Python 中提供了一套處理錯誤的機制,叫做異常
。
比較普通的處理錯誤的方法,是使用if 語句
或斷言assert
來對各種情況進行判斷,從而進行相應的處理。而異常
是一種更加高階的處理錯誤的機制。
1,常見異常
我們來看一些Python 中常見的異常。
SyntaxError
異常
Python 語言有自己的語法格式和規則,如果我們沒有遵守這些規則,將會出現異常:
>>> print('abc')- # 右括號後邊有一個橫線
File "<stdin>", line 1
print('abc')-
^
SyntaxError: invalid syntax
上尖括號^
指出了異常出現的的位置。
NameError
異常
如果我們使用了一個未定義
的變數,將會出現該異常:
>>> print(a) # a 變數未定義
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
ZeroDivisionError
異常
進行除法運算時,如果除數為0
,將會該異常:
>>> 1 / 0 # 除數為 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
如果程式沒有處理異常,在異常出現時,將會崩潰退出,Python 直譯器會為你定位到異常
出現的位置,有助於快速的解決異常。
2,處理異常
異常需要捕獲,從而處理異常。如果在發生異常時,這個異常沒有被捕獲處理,這個異常將會一層一層的向上拋,直到這個異常被捕獲處理,或者程式崩潰。
try except
語句
在Python 中使用try
語句塊來捕獲異常,在except
語句塊中處理異常。
我們一般將有可能出現異常
的語句放在try
語句塊中,在except
語句塊中編寫處理異常的措施。
比如,我們有如下程式碼:
def hello(s):
print('hello %s' % s)
hello('123', 'abc')
其中hello
函式的定義是需要接收一個引數,而在呼叫時,傳遞了兩個引數,執行此程式碼將出現如下異常:
Traceback (most recent call last):
File "Test.py", line 8, in <module>
hello('123', 'abc')
TypeError: hello() takes 1 positional argument but 2 were given
程式碼在遇到異常
時,會在異常程式碼處丟擲異常,後邊的程式碼將不會再執行。如果異常程式碼沒有處理,程式將崩潰退出。
我們可以這樣處理該異常:
try:
hello('123', 'abc')
except Exception as e:
print(e)
我們將呼叫語句
放在了try
語句塊中,這樣就可以捕獲異常。except
關鍵字的後邊是要捕獲的異常的名字
,as
後邊是捕獲到的異常 e
。在except
語句塊中,我們只是將捕獲到的異常列印了出來,執行該程式碼,結果如下:
hello() takes 1 positional argument but 2 were given
可見錯誤被列印了出來。我們還可以在列印錯誤之後,再正確的呼叫hello
函式:
try:
hello('123', 'abc')
except Exception as e:
print(e)
hello('123')
執行結果如下:
hello() takes 1 positional argument but 2 were given
hello 123
可見,執行到hello('123', 'abc')
時,出現異常,然後程式碼執行到except
語句塊,print(e)
將錯誤列印出來,又執行了hello('123')
。
列印異常是最簡單的處理異常的方式,在工作中,我們會將異常資訊記錄在日誌檔案中,這樣可以將異常記錄下來,以便處理異常。
一般在捕獲異常時,儘量只在try
語句塊中編寫有可能發生異常的程式碼,基本不會發生異常的語句不要寫在try
塊中,這樣可以減少try
塊中的程式碼量,有助於定位問題。
except
語句
except
關鍵字後邊可以跟一個異常名字
,也可以跟一組異常名字
,一組異常時,將多個異常的名字寫在一個元組中,語法如下:
except (Error1, Error2...):
pass
一個try
語句中,也可以包含多個except
語句塊,語法如下:
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
語句塊是在沒有遇到異常時執行的。
發生異常時,示例:
try:
hello('123', 'abc')
except Exception as e:
print('發生異常')
print(e)
else:
print('沒有發生異常')
上面的程式碼中,執行到hello('123', 'abc')
時會發生異常,然後會進入到except
語句塊,else
語句不會被執行,執行結果如下:
發生異常
hello() takes 1 positional argument but 2 were given
沒有發生異常時,示例:
try:
hello('123')
except Exception as e:
print('發生異常')
print(e)
else:
print('沒有發生異常')
上面程式碼中的try
語句塊不會發生異常,那麼except
語句就不會執行,else
語句會執行,結果如下:
hello 123
沒有發生異常
注意:
else
語句的使用頻率並不高。
finally
語句
finally
語句塊無論是否異常都會被執行,該語句塊經常用在需要關閉系統資源的情況下。
沒有發生異常時,示例如下:
try:
hello('123')
except Exception as e:
print('發生異常')
print(e)
else:
print('沒有發生異常')
finally:
print('執行了 finally 語句塊')
上面程式碼中,try
語句塊沒有發生異常,else
與 finally
都被執行,except
語句塊沒有執行。執行結果如下:
hello 123
沒有發生異常
執行了 finally 語句塊
發生異常時,示例如下:
try:
hello('123', 'abc')
except Exception as e:
print('發生異常')
print(e)
else:
print('沒有發生異常')
finally:
print('執行了 finally 語句塊')
上面程式碼中,try
語句塊發生異常,except
與 finally
都被執行,else
語句塊沒有執行。執行結果如下:
發生異常
hello() takes 1 positional argument but 2 were given
執行了 finally 語句塊
注意:
else
語句塊與finally
語句塊可以同時存在,也可以同時不存在,也可以一個存在一個不存在,互不影響。
3,丟擲異常
raise
異常
如果你捕獲了一個異常,卻不想徹底解決這個異常,而想將該異常向上層丟擲,可以使用raise
關鍵字。
raise
用於丟擲異常,其後可以跟一個異常物件
,或者什麼也不跟。
raise
後跟一個異常物件
:
raise Exception('這裡發生了錯誤')
raise
後什麼也不跟:
try:
hello('123', 'abc')
except Exception as e:
print('發生異常')
raise
Python 直譯器會記錄最後一個發生的異常,raise
會將最後一個異常丟擲。上面程式碼中的raise
相當於raise e
。
assert
斷言
assert
語句稱為斷言
,就是判斷某個表示式
是否為真:
- 表示式為
True
時,正常通過 - 表示式為
False
時,丟擲AssertionError
異常
示例如下:
表示式1 == 1
為True
,沒有反應:
>>> assert 1 == 1
表示式1 == 0
為False
,丟擲異常:
>>> assert 1 == 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
4,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
類。如下:
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
:
#! /usr/bin/env python3
# 引入 pdb 模組
import pdb
def hello(s):
print('hello %s' % s)
# 設定斷點
pdb.set_trace()
hello('python')
hello('java')
我們使用python3
來執行該程式,如下:
$ python3 Test.py
> ~/Test.py(12)<module>()
-> hello('python')
(Pdb)
可以看到程式碼在hello('python')
之前暫停並進入斷點,控制檯顯示出(Pdb)
,我們可以在這個後面輸入Python 程式碼或者pdb
支援的命令。
pdb
常用命令如下:
n
:進行下一步程式碼,即單步執行c
:程式碼執行到下一個斷點處,如果沒有下一個斷點,則執行到程式結束s
:在遇到函式時,使用s
命令,可以進入函式內部l
:列出當前語句周圍的10行程式碼p
:用於輸出變數的值,相當於print
函式
(完。)
推薦閱讀:
Python 簡明教程 --- 18,Python 物件導向
Python 簡明教程 --- 19,Python 類與物件
Python 簡明教程 --- 20,Python 類中的屬性與方法
Python 簡明教程 --- 21,Python 繼承與多型
Python 簡明教程 --- 22,Python 閉包與裝飾器
歡迎關注作者公眾號,獲取更多技術乾貨。