<style>td.subtitle { background-color: rgba(251, 251, 254, 1) } td.name { font-family: "Consolas" } td.center { text-align: center } td.name span { font-size: 13px; font-style: italic } td.name p { line-height: 2ex } td.example { font-family: "Consolas"; color: rgba(96, 96, 96, 1); font-size: 12px } td.example p { line-height: 2ex } td.example p span { color: rgba(176, 176, 176, 1); font-size: 12px } td.example a.advc { color: rgba(96, 96, 96, 1) } a.advc { border-bottom: 1px solid rgba(128, 128, 128, 1); text-decoration: none } a.subcata { border-bottom: 1px solid rgba(128, 128, 128, 1) } p.subcata { line-height: 30px; font-size: 18px } h2.dscp span { font-style: italic } .codebox { display: table-cell; width: 800px; padding-left: 20px; padding-right: 20px } .codebox span { color: rgba(144, 144, 144, 1) } .codebox i { fontsize: 8px } a.return { color: rgba(187, 187, 187, 1); font-size: 0.8em } code { font-family: "Courier New", Courier, monospace; font-size: 0.9em; line-height: 1.8em; border: 1px solid rgba(176, 176, 176, 1); border-radius: 2px; background: rgba(248, 248, 248, 1); padding: 2px; margin: 0 4px; vertical-align: middle }</style>
回傳目錄
本篇索引
(1)測驗的基本概念
(2)doctest模塊
(3)unittest模塊
(4)除錯器和pdb模塊
(5)程式探查
(6)調優與優化
(1)測驗的基本概念
對程式的各個部分建立測驗,這個稱為:單元測驗(unit test),更進一步的是測驗驅動編程(test-driven programming), 簡單來說,就是“先寫測驗、再寫程式”,是否一定要為所有的函式和模塊撰寫測驗代碼, 這個目前仍有爭論,因為構建完備的測驗集本身就是一件作業量很大的事情,而且常常會發生這樣的事情: 測驗還沒編好,需求已經變更了,但不管如何,測驗驅動的理念是一種進步,掌握自動化測驗工具, 比以前沒有自動化測驗工具的時代,效率要提高很多倍,
● 測驗時驅動開發的4步:
(1)指出需要的新特性,可以記錄下來,然后為其撰寫一個測驗,
(2)撰寫特性的概要代碼,程式代碼沒有任何語法錯誤,但測驗結果會失敗, 看到測驗失敗是很重要的,這樣就能確定測驗可以失敗, 這里再強調一遍:在試圖讓測驗成功之前,先要看到它失敗!
(3)為特性的概要撰寫虛設代碼(dummy code),不用準確實作功能,只要保證測驗可以通過即可,
(4)現在重寫(Refactor)代碼,真正實作需要的特性功能,之后要保證測驗一直成功,
(2)doctest模塊
函式、類或模塊的第一行如果是一個字串,這個字串就是“檔案字串”, 使用doctest模塊可以在檔案字串中查找“互動式會話”的例子, 并使用一系列測驗的形式測驗這些例子給出的資料和結果,
# my_math.py
def add2(x,y):
"""
函式名:add(x,y)
功能:回傳2個輸入引數之和
>>> add2(1, 2)
3
>>> add2(1.3, 2.4)
3.7
"""
return x+y
上例中,在自定義的add2()函式的檔案字串中,舉了2個呼叫和結果的例子, doctest模塊可以從my_math.py檔案中提取出add2()的示例部分, 加以測驗,若測驗結果和示例結果不同,則會輸出錯誤報告,
可以撰寫單獨的測驗檔案,也可以在庫模塊的末尾包含測驗代碼來測驗自身,以下分別示例:
● 撰寫單獨的測驗檔案:
# my_math.py
def add2(x,y):
...(內容同上例)
# testmymath.py (單獨測驗檔案)
import my_math
import doctest
nfails, ntests = doctest.testmod(my_math) # nfails和ntest分別表示失敗的數量和執行的總測驗數
如果所有測驗都順利通過,則不產生輸出,否則將會在螢屏上輸出錯誤報告, 如果想在螢屏上看到測驗的詳細輸出,可使用verbose引數:
doctest.testmod(my_math, verbose=True)
● 在庫模塊的末尾包含測驗代碼:
# my_math.py
def add2(x,y):
...(內容同上例)
if __name__=='__main__':
import doctest, my_math
doctest.testmod(my_math)
如果my_math.py檔案作為主程式在解釋器中運行,就會運行檔案測驗,否則, 如果檔案是由import加載的,測驗將被忽略, 如果所有測驗都順利通過,則不產生輸出,否則將會在螢屏上輸出錯誤報告, 如果想在螢屏上看到測驗的詳細輸出,可使用以下-v引數:
$ python my_math.py -v
(3)unittest模塊
對于更全面的程式測驗,可以使用unittest模塊,如果進行單元測驗, 開發人員會為程式的每個組成元素(如各個函式、方法、類和模塊)撰寫獨立的測驗案例, 然后運行這些測驗來驗證組成更大程式的基本組件的行為是否正確,
unittest的基本使用方法為:定義一個繼承自unittest.TestCase的類, 在這個類中,各種測驗由以名稱 test 開頭的方法定義,在每隔測驗內,可用各種斷言來檢查不同條件,
● TestCase實體支持以下方法和屬性:
| 實體方法 | 說明 |
|---|---|
| t.setUp() | 在運行任何測驗方法之前,呼叫它來執行設定步驟, |
| t.tearDown() | 在運行測驗之后,呼叫它來執行清除操作, |
| t.assert_(expr [,msg]) | 如果expr的計算結果為False,表明測驗失敗, msg是一條訊息字串,提供對失敗的解釋, |
| t.failUnless(expr [,msg]) | |
| t.assertEqual(x, y, [,msg]) | 如果x和y不相等,則表明測驗失敗,msg含義同上, |
| t.failUnlessEqual(x, y, [,msg]) | |
| t.assertNotEqual(x, y, [,msg]) | 如果x和y相等,則表明測驗失敗,msg含義同上, |
| t.failIfEqual(x, y, [,msg]) | |
| t.assertAlmostEqual(x, y, [,places [,msg]]) | 如果數字x和y未包含在對方的places小數位中,則表明測驗失敗, 檢查方法是計算x和y的差,并將結果舍入到給定位數,如果結果為0,則x和y的值相近, msg含義同上, |
| t.failUnlessAlmostEqual(x, y, [,places [,msg]]) | |
| t.assertNotAlmostEqual(x, y, [,places [,msg]]) | 如果x和y在places小數位內無法區分大小,則表明測驗失敗, msg含義同上, |
| t.failIfAlmostEqual(x, y, [,places [,msg]]) | |
| t.assertRaises(exc, callable, ...) | 如果可呼叫物件callable未引發一場exc,則表明測驗失敗, 剩余引數將以引數形式傳遞給callable,可以使用例外元組exc檢查多個例外, msg含義同上, |
| t.failUnlessRaises(exc, callable, ...) | |
| t.failIf(expr [,msg]) | 如果expr計算結果為True,則表明測驗失敗,msg含義同上, |
| t.fail([msg]) | 表明測驗失敗,msg含義同上, |
t.failureException |
該屬性設定為在測驗中捕獲到的最后一個例外值,用于不僅要檢查是否出現例外, 還想要檢查例外是否拋出了恰當的值, |
● 單元測驗的例子
如果需要撰寫單元測驗來測驗前面add2()函式的各個方面,可以創建一個獨立模塊 testmymath.py,如下例所示,
import my_math
import unittest
# 單元測驗
class TestMyMathFunction(unittest.TestCase):
def setUp(self):
# 執行設定操作(如果有的話)
pass
def tearDown(self):
# 執行清除操作(如果有的話)
pass
def testintint(self):
r = my_math.add2(2,3)
self.assertEqual(r, 5)
def testfloatfloat(self):
r = my_math.add2(1.2, 3.4)
self.assertEqual(r, 4.6)
def testintfloat(self):
r = my_math.add2(2, 3.5)
self.assertEqual(r, 5.5)
# 運行unittest
if __name__=='__main__':
unittest.main()
要運行單元測驗,只需在檔案 testmymath.py 上運行Python:
$ python testmymath.py
(4)除錯器和pdb模塊
Python提供了一個基于命令的簡單除錯器,pdb模塊支持:事后檢查、檢查堆疊幀、設定斷點、單步除錯以及計算代碼,
● pdb模塊的功能函式
| 函式 | 說明 |
|---|---|
| run(statement [,globals [,locals]]) | 在除錯器控制下執行statement,globals 和 locals 分別定義運行代碼的全域和區域命名空間, |
| runeval(expression [,globals [,locals]]) | 在除錯器控制下計算expression字串運算式,成功運行之后, 將回傳運算式的值,globals 和 locals 含義同上, |
| runcall(function [,argument, ...]) | 在除錯器內呼叫一個函式,function是一個可呼叫物件, 其他引數可在其后的argument部分提供,函式運行后將回傳它的回傳值, |
| set_trace() | 在呼叫該函式的位置啟動除錯器,這可用于將除錯器斷點硬編碼到程式代碼中, |
| post_mortem(traceback) | 對回溯物件traceback啟動事后檢查,通常使用sys.exc_info()等函式獲得, |
| pm() | 使用最后一個例外的回溯進入事檢查除錯, |
● 從Python互動環境啟動除錯器
啟動除錯器后,會顯示一個(Pdb)提示符:
以下為待除錯檔案
#testfile.py
def testadd(a, b):
c = a + b;
return c
以下為互動環境命令列:
>>> import pdb
>>> import testfile # 要除錯的檔案名
>>> pdb.run('testfile.testadd(1,2)')
> <string>(1)()
(Pdb)
● 除錯器命令
可以在除錯器提示符(Pdb)下使用命令,一些命令具有長短2種形式,例如, h(elp)表示h和help都是可接受的, 可用除錯命令見下表:
| 命令 | 說明 |
|---|---|
| [!]statement | 在當前堆疊幀背景關系中執行一行statement陳述句,感嘆號可以忽略, 但如果陳述句的第一個詞于除錯器命令類似,則必須使用它來避免歧義,如要設定全域變, 可在同一行上添加global命令作為前綴,如:global a; a = 1 |
| a(rgs) | 列印當前函式的引數串列, |
| alias [name [command]] | 創建名為name的別名來運行command,在command字串中, 鍵入別名時子字串'%1', '%2'等被替換為相應的引數,'%*'被替換為所有引數, ,如果沒有給定任何命令,則顯示當前別名串列,command可以很長, 甚至可以使用for回圈等(但需寫在一行內), |
| b(reak) [loc [,condition]] | 在位置loc處設定斷點,loc指定一個特定檔案名和行號, 或者指定一個模塊中的一個函式名稱,可使用以下語法:n(當前檔案中的行號); filename:n(另一個檔案中的行號); function(當前模塊中的函式名稱); module.function(某個模塊中的函式名稱); 如果省略了 loc,將列印當前的所有斷點,condition是一個運算式,
在列印斷點之前,該運算式的值必須計算為True,所有斷點都會被分配一個數字,
該數字將在完成此命令時作為輸出列印出來,這些數字可以在一些其他除錯命令中使用,
|
| cl(ear) [bpnumber [bpnumber ...]] | 清除斷點編號串列,如果沒指明斷點編號,所有斷點都會被清除, |
| commands [bpnumber] |
設定在遇到bpnumber時,將自動執行一系列除錯命令,列出要執行的命令時,
只需在后續行中鍵入它們并使用end來標記命令序列的結束即可,
如果包含continue命令,在遇到斷點時,程式將自動繼續執行,如果省略bpnumber,
將使用最后一個斷點集,
|
| condition bpnumber [condition] |
在斷點上放置一個條件,condition是一個運算式,在識別該斷點之前,
該運算式的值必須計算為True,省略該條件會清除任何以前的條件,
|
| c(ont(inue)) | 繼續執行,直到遇到下一個斷點, |
| disable [bpnumber [bpnumber ...]] | 禁用指定斷點集,可以在以后用enable命令重新啟用這些斷點, |
| d(own) | 將當前幀在堆疊跟蹤中下移一層, |
| enable [bpnumber [bpnumber ...]] | 啟用指定的斷點集, |
| h(elp) [command] | 顯示可用命令的串列,指定一個命令將回傳該命令的幫助資訊, |
| ignore bpnumber [count] | 忽略一個斷點count次, |
| j(ump) lineno | 設定要執行的下一行,只能用于同一執行幀中的不同陳述句之間移動,而且, 無法跳到某些陳述句中(如回圈中的陳述句) |
| l(ist) [first [,last]] | 列出源代碼,如果沒有引數,該命令將列出當前行前后的共11行,如果使用一個引數, 它將列出該行前后的11行,如果使用2個引數,它將列出指定范圍內的行, |
| n(ext) | 執行到當前函式中的下一行,與step命令的區別在于,
next 會將呼叫整體函式視為一行執行,而step將進入那個函式執行一行, |
| p expression | 在當前背景關系中計算運算式expression的值并列印其值, |
| q(uit) | 退出除錯器, |
| r(eturn) | 持續運行,直到當前函式回傳值, |
| run [args] | 重新啟動程式,并使用args中的命令列引數作為 sys.args 的新設定,
所有斷點和其他除錯器設定都會被保留, |
| s(tep) | 執行一行源代碼并停在被呼叫的函式內, |
| tbreak [loc [,condition]] | 設定一個臨時斷點,該斷點將在第一次到達時洗掉,
loc和condition用法同前,
|
| u(p) | 將當前幀在跟蹤中上移一層, |
| unalias name | 洗掉指定別名 |
| until | 恢復執行,直至不再控制當前執行幀,或者直至到達一個比當前行號大的行號, 例如在回圈中鍵入 until 命令,將執行回圈中的所有陳述句,直至回圈結束, |
| w(here) | 列印堆疊跟蹤, |
● 從命令列進行除錯
可以在命令列上呼叫除錯除錯某檔案,在這種情況下,啟動程式時除錯器將自動啟動,
$ python -m pdb testfile.py
如果用戶的主目錄或當前目錄中包含.pdbrc檔案,那么每次除錯器啟動時將執行該檔案, 可以使用這種方法來指定要在除錯器每次啟動時執行的除錯命令,
(5)程式探查
profile和cProfile模塊用于收集探查資訊,兩個模塊的作業方式相同, 但cProfile速度更快且更先進,程式探查可以通過以下命令列方式呼叫:
$ python -m cProfile testfile.py
運行該命令后,會在螢屏上列印出性能統計資訊,
也可以在互動命令列中或程式代碼中呼叫探查器(profiler),具體方法是使用run()函式, 其語法如下:
import cProfile cProfile.run(command [,filename])
探查器會使用exec陳述句執行command的內容,filename是輸出報告要保存到的檔案, 如果忽略該引數,報告將輸出到標準輸出,
生成報告的部分說明如下:
| 抬頭 | 說明 |
|---|---|
| primitive calls | 非遞回性函式呼叫的數量 |
| ncalls | 呼叫總數(包括自遞回),當表示為 n/m 時,n表示實際呼叫數量,m表示原始呼叫的數量, |
| tottime | 該函式消耗的時間(不含子函式) |
| percall | tototime / ncalls |
| cumtime | 函式消耗的總時間 |
| percall | cumtime / (primitive calls) |
| filename:lineno(function) | 每個函式的位置和名稱 |
通常對于普通的探查分析,cProfile模塊足夠了,如果希望保存資料和進一步分析, 可使用pstats模塊,它可以進一步分析cProfile模塊輸出的報告資訊,
>>> import pstats
>>> p = pstats.Stats('result.profile')
(6)調優與優化
● 程式運行時間測量
(1)方法一:使用Linux的time命令
可用于簡單對長時間運行的Python程式進行計時:
$ time python testfile.py
(2)方法二:在程式中直接放入統計時間代碼
time模塊的perf_counter()函式可得到本行程開始起到現在的總秒數, process_time()函式可得到本行程開始起到現在的行程運行秒數,
import time
start_proc = time.process_time() # 行程時間
start_real = time.time() # UTC時間
......
end_proc = time.process_time()
end_real = time.time()
print('%f Real Seconds" %(end_real - start_real))
print('%f Process Seconds" %(end_proc - start_proc))
(3)方法三:使用timeit()函式
如果想對一個特定陳述句進行基準測驗,可以使用timeit模塊中的timeit()函式,語法如下:
timeit(code [,setup])
其中,code引數時希望對其基準測驗的代碼,setup引數是一條陳述句, 用來設定執行環境,timeit()函式會運行這條陳述句100萬次并報告執行時間, 可以向timeit()函式提供number=count關鍵字引數來更改重復次數,
>>> from timeit import timeit
>>> timeit('math.sqrt(3.0)', 'import math')
0.10910729999886826
>>> timeit('sqrt(3.0)', 'from math import sqrt')
0.0762049000004481
timeit模塊還有一個repeat()函式,功能與timeit()相同, 但它重復測量5次并回傳一個結果串列:
>>> from timeit import repeat
>>> repeat('math.sqrt(3.0)', 'import math')
[0.07255980000081763, 0.07150849999925413, 0.07361959999980172, 0.0723534999997355, 0.08293649999905028]
● 記憶體測量
sys模塊有一個getsizeof()函式,可用于分析Python物件的記憶體占用(以位元組為單位):
>>> import sys
>>> sys.getsizeof(2)
28
>>> sys.getsizeof('a')
50
>>> sys.getsizeof([1,])
72
>>> sys.getsizeof([1,2,3])
88
>>> sum(sys.getsizeof(x) for x in [1,2,3])
84
對于串列、元組和字典等容器,報告的大小只是容器物件本身的大小,不是容器中包含的所有物件的累計大小, 可以像上面顯示的那樣使用sum()函式來計算串列內容的總大小,
測量實際記憶體占用的一種輔助技術是,從作業系統的行程查看器或任務管理器檢查正在運行的程式,
● 返匯編
dis模塊可用于將Python函式、方法、類反匯編為低級的解釋器指令, 該模塊中的dis()函式使用示例如下:
>>> from dis import dis >>> import testfile >>> dis(testfile.testadd)
在多執行緒程式中,反匯編中的每行操作都采用是原子執行方式,一行不會被中間打斷, 可以利用此資訊來跟蹤復雜的競爭條件,
● 調優策略
下面列出了一些比較典型的優化策略:
(1)盡量使用內置型別
Python內置的元組、串列、集合、字典完全是用C語言實作的,是解釋器中優化程度最高的資料結構, 盡量避免構建自定義的資料結構(如:二叉搜索樹、鏈表等)來模仿它們的功能, 標準庫中的型別也是很好的選擇,例如collection.deque雙端佇列,用它在佇列頭插入項, 比在普通串列頭部插入項要高效得多,
(2)能使用字典結構就不要定義新class
用戶定義得類和實體時用字典構建的,因此,查找、設定、洗掉實體資料的速度幾乎總是比直接 在字典上執行這些操作更慢,如果只是構建一個簡單的資料結構來存盤資料,元組和字典通常就夠用了,
(3)使用__slots__
如果程式創建了自定義類的大量實體,可以考慮在類定義中使用__slots__屬性, __slots__有時被看作一種安全功能,因為它會限制屬性名稱的設定, 但它更主要的用途是性能優化,使用__slots__的類不使用字典存盤實體資料 (而是用一種更高效的內部資料結構),所以其實體使用的記憶體也更少、訪問速度也更快,
不過,需要注意的是,將__slots__功能添加到類中可能會無故破壞其他代碼, 因為__slots__會占用__dict__屬性,故依賴__dict__的代碼會失敗,
(4)避免多次重復使用(.)運算子
使用.在物件上查找屬性時,總會涉及名稱查找,對于大量使用方法或模塊查找的計算, 最好首先將要執行的操作方道一個區域變數中,從而避免屬性查找,例如:使用 from math import sqrt 和 sqrt(x) 要比 math.sqrt(x) 要快 1.4 倍左右,
(5)使用例外來處理不常見的情況,但是避免對常見情況使用例外
對于try中陳述句塊正常運行的情況,其運行速度要比前面加一個額外的if判斷要快10%左右, 但是如果經常會讓程式陷入例外except的陳述句塊,程式就會變得非常慢, 因此,在這種情況下,用if判斷陳述句比較好,
另外,檢查字典中是否有某個鍵名,用in運算子也比用d.get(key)要快2倍,
(6)鼓勵使用函式式編程和迭代
使用:串列推導、生成器運算式、生成器、協程、閉包,這些方式會使程式執行效率大大提高, 尤其是對于大量資料處理來說,更是如此,射你用生成器撰寫的代碼,不僅運行速度快,而且記憶體使用效率也高,
(7)使用裝飾器和元類
裝飾器和元類用于修改函式和類,可以通過多種方式使用它們來改進性能, 特別是程式擁有很多可以啟動或禁用的可選功能時,
回傳目錄
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/192012.html
標籤:Python
上一篇:數值運算_第1周
