一、錯誤和異常
Python 中(至少)有兩種錯誤:語法錯誤和異常( syntax errors 和 exceptions )。
1.1 錯誤
(1)語法錯誤,也被稱作解析錯誤,無法通過python解釋器的語法檢測,必須在程序執行前就改正。比如:
>>> while True print('Hello world')
File "<stdin>", line 1, in ?
while True print('Hello world')
^
SyntaxError: invalid syntax
語法分析器指出錯誤行,並且在檢測到錯誤的位置前面顯示一個小“箭頭”。 錯誤是由箭頭 前面 的標記引起的(或者至少是這麼檢測的): 這個例子中,函數 print() 被發現存在錯誤,因爲它前面少了一個冒號( ':'
)。 錯誤會輸出文件名和行號,所以如果是從腳本輸入的你就知道去哪裏檢查錯誤了。
1.2 異常
什麼是異常?
異常即是一個事件,該事件會在程序執行過程中發生,影響了程序的正常執行。一般情況下,在Python無法正常處理程序時就會發生一個異常。異常是Python對象,表示一個錯誤。即使一條語句或表達式在語法上是正確的,當試圖執行它時也可能會引發錯誤。運行期檢測到的錯誤稱爲 異常,並且程序不會無條件的崩潰。所以我們需要異常的處理來解決這些問題。當Python腳本發生異常時我們需要捕獲處理它,否則程序會終止執行。
異常也有不同的類型,異常類型做爲錯誤信息的一部分顯示出來:零除錯誤( ZeroDivisionError ) ,命名錯誤( NameError) 和 類型錯誤( TypeError )。打印錯誤信息時,異常的類型作爲異常的內置名顯示。對於所有的內置異常都是如此,不過用戶自定義異常就不一定了(儘管這是一個很有用的約定)。標準異常名是內置的標識(沒有保留關鍵字)。後一部分是關於該異常類型的詳細說明,這意味着它的內容依賴於異常類型。錯誤信息的前半部分以堆棧的形式列出異常發生的位置。通常在堆棧中列出了源代碼行,然而,來自標準輸入的源碼不會顯示出來。
python提供了兩個非常重要的功能來處理python程序在運行中出現的異常和錯誤。你可以使用該功能來調試python程序。
- 異常捕獲和處理(try/except/finally語句塊)
- 斷言(Assertions
如果異常對象並未被處理或捕捉,程序就會用所謂的回溯(traceback)終止執行。
python2.x捕獲異常語法(只能py2):
try:
...some functions...
except Exception, e:
print(e)
python3.x捕獲異常語法(py2也可以,推薦使用,有兼容能力,性能更好):
try:
...some functions...
except Exception as e:
print(e)
二、異常捕獲和處理
2.1 try/except語句
捕獲異常可以使用try/except語句。
try/except語句用來檢測try語句塊中的錯誤,從而讓except語句捕獲異常信息並處理。如果你不想在異常發生時結束你的程序,只需在try裏捕獲它。
語法:
以下爲簡單的try....except...else的語法(可以有多個羅列的except子句,也可以在一個except中寫多個捕獲類型):
try:
<語句> #運行別的代碼
except <名字>:
<語句> #如果在try部份引發了'name'異常
except <名字>,<數據>:
<語句> #如果引發了'name'異常,獲得附加的數據
else:
<語句> #如果沒有異常發生
try的工作原理是,當開始一個try語句後,python就在當前程序的上下文中作標記,這樣當異常出現時就可以回到這裏,try子句先執行,接下來會發生什麼依賴於執行時是否出現異常。
else子句只能出現在所有 except 子句之後。當 try 語句沒有拋出異常時,需要執行一些代碼,可以使用這個子句。
- 如果當try後的語句執行時發生異常,python就跳回到try並執行第一個匹配該異常的except子句,異常處理完畢,控制流就通過了整個try語句(除非在處理異常時又引發新的異常)。
- 如果在try後的語句裏發生了異常,卻沒有匹配的except子句,異常將被遞交到上層的try,或者到程序的最上層(這樣將結束程序,並打印缺省的出錯信息)。
- 如果在try子句執行時沒有發生異常,python將執行else語句後的語句(如果有else的話),然後控制流通過整個try語句。
示例:
try:
fh = open("testfile", "w")
fh.write("這是一個測試文件,用於測試異常!!")
except IOError:
print "Error: 沒有找到文件或讀取文件失敗"
else:
print "內容寫入文件成功"
fh.close()
如果正常執行:
$ python test.py
內容寫入文件成功
$ cat testfile # 查看寫入的內容
這是一個測試文件,用於測試異常!!
如果寫文件沒有權限:
$ python test.py
Error: 沒有找到文件或讀取文件失敗
(1)使用except而不帶任何異常類型
你可以不帶任何異常類型使用except,如下實例:
try:
正常的操作
......................
except:
發生異常,執行這塊代碼
......................
else:
如果沒有異常執行這塊代碼
以上方式try-except語句捕獲所有發生的異常。但這不是一個很好的方式,我們不能通過該程序識別出具體的異常信息。因爲它捕獲所有的異常,除非不打印錯誤信息,只是爲了捕獲異常。
(2)使用except而帶多種異常類型
一個 try 語句可能包含多個 except 子句,分別指定處理不同的異常。至多隻會有一個分支被執行。異常處理程序只會處理對應的 try 子句中發生的異常,在同一個 try 語句中,其他子句中發生的異常則不作處理。一個except子句可以同時處理多個異常,這些異常將被放在一個括號裏成爲一個元組。一個 except 子句可以在括號中列出多個異常的名字,如下所示:
try:
正常的操作
......................
except(Exception1[, Exception2[,...ExceptionN]]]):
發生以上多個異常中的一個,執行這塊代碼
......................
else:
如果沒有異常執行這塊代碼
除了以上做法可能可讀性差,不能簡潔明瞭告訴我們每一個不同的異常的處理邏輯,所以我們可以使用多個except子句的如下方式 ,類似於多個catch子句:
number = input('please type a number(max=100):')
try:
number = int(number)
number = number + 1
except ValueError as e:
print('Error: {}'.format(e))
except IOError as e:
print('IOError: {}'.format(e))
finally:
print("That's all!")
注意:except子句的數量沒有限制,但使用多個except子句捕獲異常時,如果異常類之間具有繼承關係,則子類應該寫在前面,否則父類將會直接截獲子類異常。放在後面的子類異常也就不會執行。 但最多隻有一個分支會被執行,所以except子句有排序先後問題
try 語句按如下方式工作:
-
如果沒有異常發生, except 子句 在 try 語句執行完畢後就被忽略了。
-
如果在 try 子句執行過程中發生了異常,那麼該子句其餘的部分就會被忽略。
如果異常匹配於 except 關鍵字後面指定的異常類型,就執行對應的except子句。然後繼續執行 try 語句之後的代碼。
-
如果發生了一個異常,在 except 子句中沒有與之匹配的分支,它就會傳遞到上一級 try 語句中。
如果最終仍找不到對應的處理語句,它就成爲一個 未處理異常,終止程序運行,顯示提示信息。
2.2 try-finally 語句
try-finally 語句無論是否發生異常都將執行最後的代碼finally裏的代碼。
NOTE:try…finally 的意義在於,就是我們在 try 代碼塊中執行了 return 語句,但是仍然會繼續執行在 finally 中的代碼塊,所以我們一般用作處理資源的釋放,但不捕獲異常。
基本語法:
try:
<語句>
finally:
<語句> #退出try時總會執行
raise
實例:
try:
fh = open("testfile", "w")
fh.write("這是一個測試文件,用於測試異常!!")
finally:
print "Error: 沒有找到文件或讀取文件失敗"
如果打開的文件沒有可寫權限,輸出如下所示:
$ python test.py
Error: 沒有找到文件或讀取文件失敗
同樣的例子也可以寫成如下方式:
try:
fh = open("testfile", "w")
try:
fh.write("這是一個測試文件,用於測試異常!!")
finally:
print "關閉文件"
fh.close()
except IOError:
print "Error: 沒有找到文件或讀取文件失敗"
當在try塊中拋出一個異常,立即執行finally塊代碼。
finally塊中的所有語句執行後,異常被再次觸發,並執行except塊代碼。參數的內容不同於異常。
當出現類似break,return等關鍵字,會先執行完finally塊中的語句再進行break等操作。
2.3 try/except/finally語句塊
完整版的語句塊:
def div(a, b):
try:
print(a / b)
except ZeroDivisionError:
print("Error: b should not be 0 !!")
except Exception as e:
print("Unexpected Error: {}".format(e))
else:
print('Run into else only when everything goes well')
finally:
print('Always run into finally block.')
# tests
div(2, 0)
div(2, 'bad type')
div(1, 2)
Error: b should not be 0 !!
Always run into finally block.
Unexpected Error: unsupported operand type(s) for /: 'int' and 'str'
Always run into finally block.
0
Run into else only when everything goes well
Always run into finally block.
# Mutiple exception in one line
try:
print(a / b)
except (ZeroDivisionError, TypeError) as e:
print(e)
# Except block is optional when there is finally
try:
open(database)
finally:
close(database)
# catch all errors and log it
try:
do_work()
except:
# get detail from logging module
logging.exception('Exception caught!')
# get detail from sys.exc_info() method
error_type, error_value, trace_back = sys.exc_info()
print(error_value)
raise
總結如下
except
語句不是必須的,finally
語句也不是必須的,但是二者必須要有一個,否則就沒有try
的意義了。except
語句可以有多個,Python會按except
語句的順序依次匹配你指定的異常,如果異常已經處理就不會再進入後面的except
語句。類似於catch語句,按照順序匹配,所以保證最上層異常類型最小。不需要下層異常類型包含上層,但不能夠出現上層異常類型包含下層的情況,此時下層異常類型永遠無法捕獲。except
語句可以以元組形式同時指定多個異常,參見實例代碼。並且指定一個對象相當於Exception e。except
語句後面如果不指定異常類型,則默認捕獲所有異常,你可以通過logging或者sys模塊獲取當前異常。- 如果要捕獲異常後要重複拋出,請使用
raise
,後面不要帶任何參數或信息。類似於throws往上拋出。 - 不建議捕獲並拋出同一個異常,請考慮重構你的代碼。
- 不建議在不清楚邏輯的情況下捕獲所有異常,有可能你隱藏了很嚴重的問題。
- 儘量使用內置的異常處理語句來替換
try/except
語句,比如with
語句,getattr()
方法。
2.4 異常的參數
一個異常可以帶上參數,可作爲輸出的異常信息參數。具體需要捕獲異常類型和異常類型的錯誤提示。類似於Exception e的格式。這個參數是否存在、是什麼類型,依賴於異常的類型。
你可以通過except語句來捕獲異常的參數,如下所示:
Python2中:
try:
正常的操作
......................
except ExceptionType, Argument:
你可以在這輸出 Argument 的值...
注:在Python3中,原Python2的except Exception , ex
的別名方法已經不能使用,逗號被認爲是兩種異常的分隔符,而不是取別名。
Python2和Python3中:Py3不支持直接輸出參數的格式。
try:
正常的操作
......................
except ExceptionType as e:
你可以在這輸出 e 的值...
變量接收的異常值通常包含在異常的語句中。在元組的表單中變量可以接收一個或者多個值。元組通常包含錯誤字符串,錯誤數字,錯誤位置。
實例:
以下爲單個異常的實例:
# 定義函數
def temp_convert(var):
try:
return int(var)
except ValueError, Argument:
print "參數沒有包含數字\n", Argument
# 調用函數
temp_convert("xyz")
以上程序執行結果如下:
$ python test.py
參數沒有包含數字
invalid literal for int() with base 10: 'xyz'
Python3中執行:不支持這種方式。
File "untitled1.py", line 12
except ValueError, Argument:
^
SyntaxError: invalid syntax
Python 3只支持這樣方式,不支持後接參數的形式,必須指定爲一個參數。所以爲了兼容Py2和Py3,需要用下述形式,而且功能更強大。
# 定義函數
def temp_convert(var):
try:
return int(var)
except ValueError as e:
print "參數沒有包含數字\n", Argument
# 調用函數
temp_convert("xyz")
這樣做是爲 except 子句指定一個變量。這個變量綁定於一個異常實例,它存儲在 instance.args
的參數中。爲了方便起見,異常實例定義了 __str__() ,這樣就可以直接訪問過打印參數而不必引用 .args
。
2.5 具體使用方法
python中try except處理程序異常的三種常用方法
方法一:捕獲所有異常
try:
a=b
b=c
except (ZeroDivisionError,Exception):
print(ZeroDivisionError,":",Exception)
方法二:採用traceback模塊查看異常
#引入python中的traceback模塊,跟蹤錯誤
import traceback
try:
a=b
b=c
except:
traceback.print_exc()
方法三:採用sys模塊回溯最後的異常
#引入sys模塊
import sys
try:
a=b
b=c
except:
info=sys.exc_info()
print info[0],":",info[1]
但是,如果你還想把這些異常保存到一個日誌文件中,來分析這些異常,那麼請看下面的方法:
把 traceback.print_exc() 打印在屏幕上的信息保存到一個文本文件中
輸出sys.exc_type, sys.exc_value, sys.exc_traceback, limit, file等異常信息,實際上是以線程安全的方式去使用sys.exc_info()函數來獲取相同的信息。
import traceback
try:
a=b
b=c
except:
f=open("c:log.txt",'a')
traceback.print_exc(file=f)
f.flush()
f.close()
2.6 主動拋出異常
我們可以使用raise語句自己觸發異常。類似於Java的thow和thows(直接拋出自定義異常)主動拋出異常。此時可以讓上層調用者捕獲和處理該異常。更多的時候是拋出自定義類型。
raise唯一的一個參數指定了要被拋出的異常的實例,如果什麼參數都不給,那麼會默認拋出當前異常。
raise語法格式如下:
raise [Exception [, args [, traceback]]]
語句中 Exception 是異常的類型(例如,NameError)參數標準異常中任一種,args 是自已提供的異常參數。
最後一個參數是可選的(在實踐中很少使用),如果存在,是跟蹤異常對象。
作用:
(1)需要記錄錯誤信息,然後將異常繼續往上層傳遞,讓上層去處理異常(類似於Java的throws)。
(2)需要主動彈出異常,作爲警告或特殊處理(類似於Java的throw)。
實例:
一個異常可以是一個字符串,類或對象。 Python的內核提供的異常,大多數都是實例化的類,這是一個類的實例的參數。
定義一個異常非常簡單,如下所示:
def functionName( level ):
if level < 1:
raise Exception("Invalid level!", level)
# 觸發異常後,後面的代碼就不會再執行
注意:爲了能夠捕獲異常,"except"語句必須有用相同的異常來拋出類對象或者字符串。
例如我們捕獲以上異常,"except"語句如下所示:
try:
正常邏輯
except Exception,err:
觸發自定義異常
else:
其餘代碼
實例:
# 定義函數
def mye( level ):
if level < 1:
raise Exception,"Invalid level!"
# 觸發異常後,後面的代碼就不會再執行
try:
mye(0) # 觸發異常
except Exception,err:
print 1,err
else:
print 2
執行以上代碼,輸出結果爲:
$ python test.py
1 Invalid level!
一般爲了形成捕獲鏈,重新拋出捕獲的異常,都是用raise,不加參數。
2.7 用戶自定義異常
通過創建一個新的異常類,程序可以命名它們自己的異常。異常應該是典型的繼承自Exception類,通過直接或間接的方式。異常的名字都以Error
結尾,我們在爲自定義異常命名的時候也需要遵守這一規範,就跟標準的異常命名一樣。
以下爲與RuntimeError相關的實例,實例中創建了一個類,基類爲RuntimeError,用於在異常觸發時輸出更多的信息。
異常類中可以定義任何其它類中可以定義的東西,但是通常爲了保持簡單,只在其中加入幾個屬性信息,以供異常處理句柄提取。如果一個新創建的模塊中需要拋出幾種不同的錯誤時,一個通常的作法是爲該模塊定義一個異常基類,然後針對不同的錯誤類型派生出對應的異常子類。
在try語句塊中,用戶自定義的異常後執行except塊語句,變量 e 是用於創建Networkerror類的實例。
class Networkerror(RuntimeError):
def __init__(self, arg):
self.args = arg
在你定義以上類後,你可以觸發該異常,如下所示:
try:
raise Networkerror("Bad hostname")
except Networkerror,e:
print e.args
2.8 Python 標準異常類
Python解析器會自動將通用異常類型名稱放在內建命名空間中。
異常名稱 | 描述 |
---|---|
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 | 用戶代碼生成的警告 |
異常類型組織結構如下:
1 BaseException 2 +-- SystemExit 3 +-- KeyboardInterrupt 4 +-- GeneratorExit 5 +-- Exception 6 +-- StopIteration 7 +-- StandardError 8 | +-- BufferError 9 | +-- ArithmeticError 10 | | +-- FloatingPointError 11 | | +-- OverflowError 12 | | +-- ZeroDivisionError 13 | +-- AssertionError 14 | +-- AttributeError 15 | +-- EnvironmentError 16 | | +-- IOError 17 | | +-- OSError 18 | | +-- WindowsError (Windows) 19 | | +-- VMSError (VMS) 20 | +-- EOFError 21 | +-- ImportError 22 | +-- LookupError 23 | | +-- IndexError 24 | | +-- KeyError 25 | +-- MemoryError 26 | +-- NameError 27 | | +-- UnboundLocalError 28 | +-- ReferenceError 29 | +-- RuntimeError 30 | | +-- NotImplementedError 31 | +-- SyntaxError 32 | | +-- IndentationError 33 | | +-- TabError 34 | +-- SystemError 35 | +-- TypeError 36 | +-- ValueError 37 | +-- UnicodeError 38 | +-- UnicodeDecodeError 39 | +-- UnicodeEncodeError 40 | +-- UnicodeTranslateError 41 +-- Warning 42 +-- DeprecationWarning 43 +-- PendingDeprecationWarning 44 +-- RuntimeWarning 45 +-- SyntaxWarning 46 +-- UserWarning 47 +-- FutureWarning 48 +-- ImportWarning 49 +-- UnicodeWarning 50 +-- BytesWarning
三、高級使用方法
3.1 傳遞異常 re-raise Exception
捕捉到了異常,但是又想重新拋出它(傳遞異常),使用不帶參數的raise
語句即可:
def f1():
print(1/0)
def f2():
try:
f1()
except Exception as e:
raise # don't raise e !!!
f2()
在Python2中,爲了保持異常的完整信息,那麼你捕獲後再次拋出時千萬不能在raise
後面加上異常對象,否則你的trace
信息就會從此處截斷。以上是最簡單的重新拋出異常的做法,也是推薦的做法。
還有一些技巧可以考慮,比如拋出異常前你希望對異常的信息進行更新。
def f2():
try:
f1()
except Exception as e:
e.args += ('more info',)
raise
Python3對重複傳遞異常有所改進,你可以自己嘗試一下,不過建議還是遵循以上規則。
3.2 Exception 和 BaseException
當我們要捕獲一個通用異常時,應該用Exception
還是BaseException
?這兩個異常到底有啥區別呢? 請看它們之間的繼承關係。
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration...
+-- StandardError...
+-- Warning...
從Exception
的層級結構來看,BaseException
是最基礎的異常類,Exception
繼承了它。BaseException
除了包含所有的Exception
外還包含了SystemExit
,KeyboardInterrupt
和GeneratorExit
三個異常。
由此看來你的程序在捕獲所有異常時更應該使用Exception
而不是BaseException
,因爲被排除的三個異常屬於更高級別的異常,合理的做法應該是交給Python的解釋器處理。
3.3 except Exception as e和 except Exception, e
as 表示將異常重命名。
代碼示例如下:
try:
do_something()
except NameError as e: # should
pass
except KeyError, e: # should not
pass
在Python2的時代,你可以使用以上兩種寫法中的任意一種。在Python3中你只能使用第一種寫法,第二種寫法已經不再支持。第一個種寫法可讀性更好,而且爲了程序的兼容性和後期移植的成本,請你果斷拋棄第二種寫法。
3.4 raise "Exception string"
把字符串當成異常拋出看上去是一個非常簡潔的辦法,但其實是一個非常不好的習慣。
if is_work_done():
pass
else:
raise "Work is not done!" # not cool
上面的語句如果拋出異常,那麼會是這樣的:
Traceback (most recent call last):
File "/demo/exception_hanlding.py", line 48, in <module>
raise "Work is not done!"
TypeError: exceptions must be old-style classes or derived from BaseException, not str
這在 Python2.4 以前是可以接受的做法,但是沒有指定異常類型有可能會讓下游沒辦法正確捕獲並處理這個異常,從而導致你的程序難以維護。簡單說,這種寫法是過時的,應該摒棄,應該拋出一個具體類型。
3.5 使用內置的語法範式代替try/except
Python 本身提供了很多的語法範式簡化了異常的處理,比如for
語句就處理了的StopIteration
異常,讓你很流暢地寫出一個循環。with
語句在打開文件後會自動調用finally
並關閉文件(預定義清理行爲)。我們在寫 Python 代碼時應該儘量避免在遇到這種情況時還使用try/except/finally的思維來處理。
如果with語句塊中觸發異常,會調用默認的異常處理器處理,而且文件仍然能夠正常關閉。
# should not
try:
f = open(a_file)
do_something(f)
finally:
f.close()
# should
with open(a_file) as f:
do_something(f)
再比如,當我們需要訪問一個不確定的屬性時,有可能你會寫出這樣的代碼:
try:
test = Test()
name = test.name # not sure if we can get its name
except AttributeError:
name = 'default'
其實你可以使用更簡單的getattr()
來達到你的目的。
name = getattr(test, 'name', 'default')
在第一個方法中需要在finally子句中再判斷f是否爲空,然後確定是否關閉。
四、斷言
斷言(assert):當程序運行到某個節點的時候,就斷定某個變量的值必然是什麼,或者是對象必然擁有某個屬性等。簡單點來說的話,就是斷定是什麼東西就必然是什麼東西,如果不是,就拋出斷言錯誤異常。
斷言應該用於如下情況:
- 防禦性的編程
- 運行時對程序邏輯的檢測
- 合約性檢查(比如前置條件,後置條件)
- 程序中的常量
- 檢查文檔
在測試用例中,執行完測試用例後,最後一步是判斷測試結果是pass還是fail,自動化測試腳本里面一般把這種生成測試結果的方法稱爲斷言。
4.1 基本使用方法
assert語句根據後面的表達式的真假來控制程序流。若爲True,則往下執行。若爲False,則中斷程序並調用默認的異常處理器,同時輸出指定的提示信息。也就是觸發一個帶可選錯誤信息的AssertionError。
assert斷言語句爲raise-if-not,用來測試表示式,其返回值爲假,就會觸發異常。
格式:
assert expression,'information'
Example:
#!/usr/bin/env python
def testAssert(x):
assert x < 1,'Invalid value'
testAssert(1)
print 'Valid value'
output:
AssertionError: Invaild value
4.2 斷言函數
以上是基本斷言用法。還有一些已經定義好的斷言函數可以使用。
常用的斷言函數:
五、異常處理和斷言的對比
5.1 什麼時候使用斷言
(1)可以使用斷言去檢查代碼中的確定量。
確定量怎麼理解?在當前情境下,確定量總會滿足特定條件。比如,在某段代碼裏,變量 x 的值始終應該在 0 到 1 之間。
(2)斷言能夠幫助別人或未來的你理解代碼,找出程序中邏輯不對的地方。
一方面,斷言會提醒你某個對象應該處於何種狀態,另一方面,如果某個時候斷言爲假,會拋出 AssertionError 異常,很有可能終止程序。
(3)參數校驗
有的時候斷言會被用到函數參數校驗的地方,我們應該避免這種使用方式。因爲,如果 Python 以 -O 選項執行的話,assert 語句會被跳過。這樣的話,我們的校驗語句都會失效。斷言爲假觸發的異常的語義相比於一般的異常也不太明確。比如,當需要使用 int 類型的時候用戶傳入 string 類型手動觸發一個 TypeError ,或是當需要正數但接收到負數的時候手動觸發一個 ValueError ,都比 AssertionError好理解一些。
5.2 小結
- 使用斷言去檢測程序中理論上不應該出現的情況。注意 Python 帶 -O 選項執行時,斷言會被忽略。
- 當由用戶輸入或者外部環境(比如網絡延遲、文件不存在等)引起的問題時,我們應拋出異常。
- 儘量避免拋出或捕獲 [Base]Exception 。
六、總結
(1)總體來說使用方法類似於Java的try/catch/finally,只不過把catch換成了except。異常類型,參數,自定義異常類,主動拋出異常等Java都有。
(2)有一個異常處理的問題,不管是Python,Java還是C++等都需要考慮。因爲本人是學信息安全的,所以很多時候需要考慮安全問題。如果在一個公司的實際的項目裏,每一次都打印異常信息,將是一件很危險的事,會給黑客提供很多信息量;而且也不是一個用戶友好的開發實現。所以一是保證異常捕獲不暴露具體信息,二是要形成一個異常捕獲鏈,傳遞異常。
(3)避免在catch
語句塊中幹一些沒意義的事情,捕獲異常也是需要成本的。
(4)只處理你知道的異常,避免捕獲所有異常然後吞掉它們,而你並不知道這個錯誤。
(5)不要使用異常來控制流程,那樣你的程序會無比難懂和難維護,可讀性很差。
(6)如果有需要,切記使用finally
來釋放資源。
(7)如果有需要,請不要忘記在處理異常後做清理工作或者回滾操作。