要么做第一個(gè),要么做最好的一個(gè)。
目錄

我們?cè)诰帉?xiě)程序時(shí),總會(huì)不自覺(jué)的出現(xiàn)一些錯(cuò)誤,比如邏輯錯(cuò)誤,語(yǔ)法錯(cuò)誤和一些其它的運(yùn)行時(shí)錯(cuò)誤等。
-
邏輯錯(cuò)誤: 這種錯(cuò)誤不會(huì)導(dǎo)致程序
崩潰,它不容易被發(fā)現(xiàn),只有在執(zhí)行結(jié)果不是我們預(yù)期的時(shí)候,才會(huì)被發(fā)現(xiàn)。 -
語(yǔ)法錯(cuò)誤: 這種錯(cuò)誤是不符合語(yǔ)法規(guī)定的錯(cuò)誤,說(shuō)白了,就是
編譯器或者解釋器無(wú)法理解的代碼。出現(xiàn)這種錯(cuò)誤時(shí),程序是不能運(yùn)行的。 - 其它運(yùn)行時(shí)錯(cuò)誤: 這種錯(cuò)誤是程序在運(yùn)行的過(guò)程中出現(xiàn)的,一般情況下不會(huì)出現(xiàn),但是極端情況下會(huì)出現(xiàn),是程序編寫(xiě)者考慮不夠周全導(dǎo)致的。
在寫(xiě)程序時(shí)一定要把所有的情況都考慮到,并且處理掉,不能有僥幸心理(認(rèn)為某種情況不會(huì)出現(xiàn))。在程序中,只要是有可能出現(xiàn)的情況,那就一定會(huì)出現(xiàn)。
程序員也喜歡將這些錯(cuò)誤戲稱(chēng)為bug,bug代表軟件系統(tǒng)中的漏洞或缺陷,bug需要修正,否則程序是無(wú)法正常運(yùn)行的。重要的軟件系統(tǒng)如果出現(xiàn)漏洞,會(huì)帶來(lái)巨大的危害。因此,在軟件初步完成后,要進(jìn)行嚴(yán)格的,全面的測(cè)試,否則將漏洞百出。
Python 中提供了一套處理錯(cuò)誤的機(jī)制,叫做異常。
比較普通的處理錯(cuò)誤的方法,是使用if 語(yǔ)句 或斷言assert來(lái)對(duì)各種情況進(jìn)行判斷,從而進(jìn)行相應(yīng)的處理。而異常是一種更加高級(jí)的處理錯(cuò)誤的機(jī)制。
1,常見(jiàn)異常
我們來(lái)看一些Python 中常見(jiàn)的異常。
SyntaxError 異常
Python 語(yǔ)言有自己的語(yǔ)法格式和規(guī)則,如果我們沒(méi)有遵守這些規(guī)則,將會(huì)出現(xiàn)異常:
>>> print('abc')- # 右括號(hào)后邊有一個(gè)橫線(xiàn)
File "<stdin>", line 1
print('abc')-
^
SyntaxError: invalid syntax
上尖括號(hào)^指出了異常出現(xiàn)的的位置。
NameError 異常
如果我們使用了一個(gè)未定義的變量,將會(huì)出現(xiàn)該異常:
>>> print(a) # a 變量未定義
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
ZeroDivisionError 異常
進(jìn)行除法運(yùn)算時(shí),如果除數(shù)為0,將會(huì)該異常:
>>> 1 / 0 # 除數(shù)為 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
如果程序沒(méi)有處理異常,在異常出現(xiàn)時(shí),將會(huì)崩潰退出,Python 解釋器會(huì)為你定位到異常出現(xiàn)的位置,有助于快速的解決異常。
2,處理異常
異常需要捕獲,從而處理異常。如果在發(fā)生異常時(shí),這個(gè)異常沒(méi)有被捕獲處理,這個(gè)異常將會(huì)一層一層的向上拋,直到這個(gè)異常被捕獲處理,或者程序崩潰。
try except 語(yǔ)句
在Python 中使用try 語(yǔ)句塊來(lái)捕獲異常,在except 語(yǔ)句塊中處理異常。
我們一般將有可能出現(xiàn)異常的語(yǔ)句放在try 語(yǔ)句塊中,在except 語(yǔ)句塊中編寫(xiě)處理異常的措施。
比如,我們有如下代碼:
def hello(s):
print('hello %s' % s)
hello('123', 'abc')
其中hello 函數(shù)的定義是需要接收一個(gè)參數(shù),而在調(diào)用時(shí),傳遞了兩個(gè)參數(shù),運(yùn)行此代碼將出現(xiàn)如下異常:
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
代碼在遇到異常時(shí),會(huì)在異常代碼處拋出異常,后邊的代碼將不會(huì)再執(zhí)行。如果異常代碼沒(méi)有處理,程序?qū)⒈罎⑼顺觥?/p>
我們可以這樣處理該異常:
try:
hello('123', 'abc')
except Exception as e:
print(e)
我們將調(diào)用語(yǔ)句放在了try 語(yǔ)句塊中,這樣就可以捕獲異常。except 關(guān)鍵字的后邊是要捕獲的異常的名字,as 后邊是捕獲到的異常 e。在except 語(yǔ)句塊中,我們只是將捕獲到的異常打印了出來(lái),運(yùn)行該代碼,結(jié)果如下:
hello() takes 1 positional argument but 2 were given
可見(jiàn)錯(cuò)誤被打印了出來(lái)。我們還可以在打印錯(cuò)誤之后,再正確的調(diào)用hello 函數(shù):
try:
hello('123', 'abc')
except Exception as e:
print(e)
hello('123')
運(yùn)行結(jié)果如下:
hello() takes 1 positional argument but 2 were given
hello 123
可見(jiàn),運(yùn)行到hello('123', 'abc') 時(shí),出現(xiàn)異常,然后代碼執(zhí)行到except 語(yǔ)句塊,print(e) 將錯(cuò)誤打印出來(lái),又執(zhí)行了hello('123')。
打印異常是最簡(jiǎn)單的處理異常的方式,在工作中,我們會(huì)將異常信息記錄在日志文件中,這樣可以將異常記錄下來(lái),以便處理異常。
一般在捕獲異常時(shí),盡量只在try 語(yǔ)句塊中編寫(xiě)有可能發(fā)生異常的代碼,基本不會(huì)發(fā)生異常的語(yǔ)句不要寫(xiě)在try 塊中,這樣可以減少try 塊中的代碼量,有助于定位問(wèn)題。
except 語(yǔ)句
except 關(guān)鍵字后邊可以跟一個(gè)異常名字,也可以跟一組異常名字,一組異常時(shí),將多個(gè)異常的名字寫(xiě)在一個(gè)元組中,語(yǔ)法如下:
except (Error1, Error2...):
pass
一個(gè)try 語(yǔ)句中,也可以包含多個(gè)except 語(yǔ)句塊,語(yǔ)法如下:
try:
# 代碼塊
except Error1 as e:
# 處理 Error1
except Error2 as e:
# 處理 Error2
.
.
.
except:
e = sys.exc_info()[0])
pass
多個(gè)except 語(yǔ)句塊時(shí),Python 解釋器會(huì)從上到下依次判斷異常的類(lèi)型,直到符合某個(gè)異常時(shí),會(huì)執(zhí)行對(duì)應(yīng)的語(yǔ)句塊中的代碼,在該except 塊之后的except 塊將被忽略。
其中,最后一個(gè)except 塊可以省略異常的名字,這種格式可以匹配任意的異常,在該塊中,可以使用sys.exc_info()[0] 來(lái)獲取異常。
在有多個(gè)except 語(yǔ)句塊時(shí),要注意,前邊的異常的范圍應(yīng)該小于等于后邊的異常的范圍,否則,后邊的except 塊將沒(méi)有意義。
else 語(yǔ)句
Python 中,try... except... 之后還可以有一個(gè)else語(yǔ)句塊,except 語(yǔ)句塊是在遇到異常時(shí)執(zhí)行的,else 語(yǔ)句塊是在沒(méi)有遇到異常時(shí)執(zhí)行的。
發(fā)生異常時(shí),示例:
try:
hello('123', 'abc')
except Exception as e:
print('發(fā)生異常')
print(e)
else:
print('沒(méi)有發(fā)生異常')
上面的代碼中,執(zhí)行到hello('123', 'abc') 時(shí)會(huì)發(fā)生異常,然后會(huì)進(jìn)入到except 語(yǔ)句塊,else 語(yǔ)句不會(huì)被執(zhí)行,執(zhí)行結(jié)果如下:
發(fā)生異常
hello() takes 1 positional argument but 2 were given
沒(méi)有發(fā)生異常時(shí),示例:
try:
hello('123')
except Exception as e:
print('發(fā)生異常')
print(e)
else:
print('沒(méi)有發(fā)生異常')
上面代碼中的try 語(yǔ)句塊不會(huì)發(fā)生異常,那么except 語(yǔ)句就不會(huì)執(zhí)行,else 語(yǔ)句會(huì)執(zhí)行,結(jié)果如下:
hello 123
沒(méi)有發(fā)生異常
注意:
else語(yǔ)句的使用頻率并不高。
finally 語(yǔ)句
finally 語(yǔ)句塊無(wú)論是否異常都會(huì)被執(zhí)行,該語(yǔ)句塊經(jīng)常用在需要關(guān)閉系統(tǒng)資源的情況下。
沒(méi)有發(fā)生異常時(shí),示例如下:
try:
hello('123')
except Exception as e:
print('發(fā)生異常')
print(e)
else:
print('沒(méi)有發(fā)生異常')
finally:
print('執(zhí)行了 finally 語(yǔ)句塊')
上面代碼中,try 語(yǔ)句塊沒(méi)有發(fā)生異常,else 與 finally 都被執(zhí)行,except 語(yǔ)句塊沒(méi)有執(zhí)行。運(yùn)行結(jié)果如下:
hello 123
沒(méi)有發(fā)生異常
執(zhí)行了 finally 語(yǔ)句塊
發(fā)生異常時(shí),示例如下:
try:
hello('123', 'abc')
except Exception as e:
print('發(fā)生異常')
print(e)
else:
print('沒(méi)有發(fā)生異常')
finally:
print('執(zhí)行了 finally 語(yǔ)句塊')
上面代碼中,try 語(yǔ)句塊發(fā)生異常,except 與 finally 都被執(zhí)行,else 語(yǔ)句塊沒(méi)有執(zhí)行。運(yùn)行結(jié)果如下:
發(fā)生異常
hello() takes 1 positional argument but 2 were given
執(zhí)行了 finally 語(yǔ)句塊
注意:
else語(yǔ)句塊與finally語(yǔ)句塊可以同時(shí)存在,也可以同時(shí)不存在,也可以一個(gè)存在一個(gè)不存在,互不影響。
3,拋出異常
raise 異常
如果你捕獲了一個(gè)異常,卻不想徹底解決這個(gè)異常,而想將該異常向上層拋出,可以使用raise 關(guān)鍵字。
raise 用于拋出異常,其后可以跟一個(gè)異常對(duì)象,或者什么也不跟。
raise 后跟一個(gè)異常對(duì)象:
raise Exception('這里發(fā)生了錯(cuò)誤')
raise 后什么也不跟:
try:
hello('123', 'abc')
except Exception as e:
print('發(fā)生異常')
raise
Python 解釋器會(huì)記錄最后一個(gè)發(fā)生的異常,raise 會(huì)將最后一個(gè)異常拋出。上面代碼中的raise 相當(dāng)于raise e。
assert 斷言
assert 語(yǔ)句稱(chēng)為斷言,就是判斷某個(gè)表達(dá)式是否為真:
- 表達(dá)式為
True時(shí),正常通過(guò) - 表達(dá)式為
False時(shí),拋出AssertionError異常
示例如下:
表達(dá)式1 == 1為True,沒(méi)有反應(yīng):
>>> assert 1 == 1
表達(dá)式1 == 0為False,拋出異常:
>>> assert 1 == 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
4,Python 異常層次結(jié)構(gòu)
Python 異常層次結(jié)構(gòu)如下:
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
這些都是內(nèi)建異常,BaseException 是所有異常的父類(lèi),我們使用最多的是Exception 及其子類(lèi)??梢允褂?code>help(類(lèi)名)來(lái)查看每個(gè)類(lèi)的詳情。
5,自定義異常
有時(shí)候,我們需要定義自己的異常類(lèi),來(lái)滿(mǎn)足自己的需求。
我們已經(jīng)知道,Python 異常類(lèi)有自己的層次結(jié)構(gòu),所有的類(lèi)都直接或者間接繼承了BaseException。因此,用戶(hù)自定義的異常類(lèi),也需要滿(mǎn)足這種層次結(jié)構(gòu)。
一般情況下,自定義異常需要繼承Exception 類(lèi)。如下:
class MyError(Exception):
pass
MyError 類(lèi)的使用方式,跟內(nèi)建異常類(lèi)的使用方式一樣。你可以根據(jù)自己的需要,為MyError 類(lèi)編寫(xiě)相應(yīng)的構(gòu)造方法,和其它類(lèi)方法。
如果沒(méi)有為MyError 編寫(xiě)構(gòu)造方法,那么MyError 就繼承了Exception 的構(gòu)造方法。
6,調(diào)試錯(cuò)誤
程序編寫(xiě)完成后不一定是正確的,當(dāng)發(fā)現(xiàn)有錯(cuò)誤時(shí),就需要定位錯(cuò)誤的位置。
最普遍,最簡(jiǎn)單的調(diào)錯(cuò)的方法就是打印某個(gè)變量,通過(guò)輸出變量的值,來(lái)查看其是否是你想要的結(jié)果。
另一種比較高效,有力的調(diào)試代碼的方式是單步調(diào)試,即是通過(guò)設(shè)置斷點(diǎn),深入到代碼內(nèi)部,一步一步的跟蹤查看代碼的執(zhí)行結(jié)果是否正確,從而達(dá)到修正代碼的目的。
在C 語(yǔ)言中有一個(gè)非常著名的工具叫做gdb,這是一款強(qiáng)大的調(diào)試工具。Python 中也有類(lèi)似的一款工具叫做pdb,它使用起來(lái)要比gdb 簡(jiǎn)單許多。
在Python 中,pdb 是一個(gè)模塊,所以,在使用之前要先使用import pdb 將該模塊引入。然后,在需要調(diào)試代碼的地方,使用pdb.set_trace() 方法來(lái)設(shè)置斷點(diǎn),在代碼執(zhí)行到此處時(shí),Python 解釋器就會(huì)從此處開(kāi)始讓你調(diào)式代碼。
如下代碼,文件名為Test.py:
#! /usr/bin/env python3
# 引入 pdb 模塊
import pdb
def hello(s):
print('hello %s' % s)
# 設(shè)置斷點(diǎn)
pdb.set_trace()
hello('python')
hello('java')
我們使用python3 來(lái)運(yùn)行該程序,如下:
$ python3 Test.py
> ~/Test.py(12)<module>()
-> hello('python')
(Pdb)
可以看到代碼在hello('python')之前暫停并進(jìn)入斷點(diǎn),控制臺(tái)顯示出(Pdb),我們可以在這個(gè)后面輸入Python 代碼或者pdb 支持的命令。
pdb 常用命令如下:
-
n:進(jìn)行下一步代碼,即單步執(zhí)行 -
c:代碼執(zhí)行到下一個(gè)斷點(diǎn)處,如果沒(méi)有下一個(gè)斷點(diǎn),則執(zhí)行到程序結(jié)束 -
s:在遇到函數(shù)時(shí),使用s命令,可以進(jìn)入函數(shù)內(nèi)部 -
l:列出當(dāng)前語(yǔ)句周?chē)?0行代碼 -
p:用于輸出變量的值,相當(dāng)于print函數(shù)
(完。)
推薦閱讀:
Python 簡(jiǎn)明教程 ---18,Python 面向?qū)ο?/a>
Python 簡(jiǎn)明教程 ---19,Python 類(lèi)與對(duì)象
Python 簡(jiǎn)明教程 ---20,Python 類(lèi)中的屬性與方法
Python 簡(jiǎn)明教程 ---21,Python 繼承與多態(tài)
Python 簡(jiǎn)明教程 ---22,Python 閉包與裝飾器