主調(diào)函數(shù)是本身固有的數(shù),被調(diào)函數(shù)是通過其他數(shù)計(jì)算出來的

創(chuàng)新互聯(lián),專注為中小企業(yè)提供官網(wǎng)建設(shè)、營銷型網(wǎng)站制作、響應(yīng)式網(wǎng)站開發(fā)、展示型成都網(wǎng)站建設(shè)、成都網(wǎng)站制作等服務(wù),幫助中小企業(yè)通過網(wǎng)站體現(xiàn)價(jià)值、有效益。幫助企業(yè)快速建站、解決網(wǎng)站建設(shè)與網(wǎng)站營銷推廣問題。
主調(diào)函數(shù) 調(diào)用別的函數(shù)實(shí)現(xiàn)功能
例如 A是主調(diào)函數(shù) B是被調(diào)函數(shù)
void A()
{
B();
}
本文章講述了如何用pdb進(jìn)行python調(diào)試講解.
當(dāng)手邊
沒有IDE,面對(duì)著python調(diào)試犯愁時(shí),你就可以參考下本文;(pdb 命令調(diào)試)
和 (pdb)help
用pdb進(jìn)行python調(diào)試,用法基本和gdb差不多,
先看一個(gè)簡(jiǎn)單的例子:
epdb1.py .# epdb1.py -- experiment with the Python debugger, pdb
a = "aaa"
b = "bbb"
c = "ccc"
final = a + b + c
print final
比如要對(duì)這個(gè)程序進(jìn)行調(diào)試:
1:在文件前面加上這一句,引入調(diào)試的模塊。
import pdb
2:在要開始調(diào)試的一行加上pdb.set_trace()文件變成:
# epdb1.py -- experiment with the Python debugger, pdb
import pdb
a = "aaa"
pdb.set_trace()
b = "bbb"
c = "ccc"
final = a + b + c
print final 可以運(yùn)行這個(gè)程序,到斷點(diǎn)出會(huì)停下來,和gdb類似,
可以執(zhí)行命令:
直接回車是重復(fù)前一條命令!
p(print) 查看一個(gè)變量值
n(next) 下一步
s(step) 單步,可進(jìn)入函數(shù)
c(continue)繼續(xù)前進(jìn)
l(list)看源代碼
用pdb調(diào)試有多種方式可選:
1.命令行啟動(dòng)目標(biāo)程序,加上-m參數(shù),這樣調(diào)用myscript.py的話斷點(diǎn)就是程序的執(zhí)行第一行之前
python -m pdb myscript.py
2. 在Python交互環(huán)境中啟用調(diào)試
import pdb
import mymodule
pdb.run('mymodule.test()')
3.比較常用的,就是在程序中間插入一段程序,相對(duì)于在一般IDE里面打上斷點(diǎn)然后啟動(dòng)debug,不過這種方式是hardcode的 if __name__ == "__main__" :
a = 1
importpdb
pdb .set_trace()
b = 2
c = a + b
print( c)
然后正常運(yùn)行腳本,到了pdb.set_trace()那就會(huì)定下來,就可以看到調(diào)試的提示符(Pdb)了
常用的調(diào)試命令 h(elp),會(huì)打印當(dāng)前版本Pdb可用的命令,如果要查詢某個(gè)命令,可以輸入 h [command],例如:"h l" - 查看list命令
l(ist),可以列出當(dāng)前將要運(yùn)行的代碼塊
(Pdb) l
497 pdb.set_trace()
498 base_data = {}
499 new_data = {}
500 try:
501 execfile(base_file_name,{},base_data)
502 - execfile(new_file_name,{},new_data)
503 except:
504 logger.writeLog("error! load result log error!")
505 print "load cmp logs error!"
506 raise Exception, "load cmp logs error!"
507斷點(diǎn)設(shè)置
(Pdb)b10 #斷點(diǎn)設(shè)置在本py的第10行
或(Pdb)bots.py:20 #斷點(diǎn)設(shè)置到 ots.py第20行
刪除斷點(diǎn)(Pdb)b #查看斷點(diǎn)編號(hào)
(Pdb)cl 2 #刪除第2個(gè)斷點(diǎn)
運(yùn)行
(Pdb)n #單步運(yùn)行
(Pdb)s #細(xì)點(diǎn)運(yùn)行 也就是會(huì)下到,方法
(Pdb)c #跳到下個(gè)斷點(diǎn)
查看
(Pdb)p param #查看當(dāng)前 變量值
(Pdb)l #查看運(yùn)行到某處代碼
(Pdb)a #查看全部棧內(nèi)變量 b(reak), 設(shè)置斷點(diǎn),例如 "b 77″,就是在當(dāng)前腳本的77行打上斷點(diǎn),還能輸入函數(shù)名作為參數(shù),斷點(diǎn)就打到具體的函數(shù)入口,如果只敲b,會(huì)顯示現(xiàn)有的全部斷點(diǎn)
(Pdb) b 504
Breakpoint 4 at /home/jchen/regression/regressionLogCMP.py:504 condition bpnumber [condition],設(shè)置條件斷點(diǎn),下面語句就是對(duì)第4個(gè)斷點(diǎn)加上條件"a==3"
(Pdb) condition 4 a==3
(Pdb) b
Num Type Disp Enb Where
4 breakpoint keep yes at /home/jchen/regression/regressionLogCMP.py:504
stop only if a==3 cl(ear),如果后面帶有參數(shù),就是清除指定的斷點(diǎn)(我在Python2.4上從來沒成功過!!!);如果不帶參數(shù)就是清除所有的斷點(diǎn)
(Pdb) cl
Clear all breaks? y disable/enable,禁用/激活斷點(diǎn)
(Pdb) disable 3
(Pdb) b
Num Type Disp Enb Where
3 breakpoint keep no at /home/jchen/regression/regressionLogCMP.py:505 n(ext),讓程序運(yùn)行下一行,如果當(dāng)前語句有一個(gè)函數(shù)調(diào)用,用n是不會(huì)進(jìn)入被調(diào)用的函數(shù)體中的
s(tep),跟n相似,但是如果當(dāng)前有一個(gè)函數(shù)調(diào)用,那么s會(huì)進(jìn)入被調(diào)用的函數(shù)體中
c(ont(inue)),讓程序正常運(yùn)行,直到遇到斷點(diǎn)
j(ump),讓程序跳轉(zhuǎn)到指定的行數(shù)
(Pdb) j 497
/home/jchen/regression/regressionLogCMP.py(497)com pareLog()
- pdb.set_trace() a(rgs),打印當(dāng)前函數(shù)的參數(shù)
(Pdb) a
_logger =
_base = ./base/MRM-8137.log
_new = ./new/MRM-8137.log
_caseid = 5550001
_toStepNum = 10
_cmpMap = {'_bcmpbinarylog': 'True', '_bcmpLog': 'True', '_bcmpresp': 'True'} p,最有用的命令之一,打印某個(gè)變量
(Pdb) p _new
u'./new/MRM-8137.log' !,感嘆號(hào)后面跟著語句,可以直接改變某個(gè)變量
q(uit),退出調(diào)試
==============================================================================================
在python中使用pdb模塊可以進(jìn)行調(diào)試
import pdb
pdb.set_trace()
也可以使用python -m pdb mysqcript.py這樣的方式
(Pdb) 會(huì)自動(dòng)停在第一行,等待調(diào)試,這時(shí)你可以看看 幫助
(Pdb) h
說明下這幾個(gè)關(guān)鍵 命令
斷點(diǎn)設(shè)置
(Pdb)b 10 #斷點(diǎn)設(shè)置在本py的第10行
或(Pdb)b ots.py:20 #斷點(diǎn)設(shè)置到 ots.py第20行
刪除斷點(diǎn)(Pdb)b #查看斷點(diǎn)編號(hào)
(Pdb)cl 2 #刪除第2個(gè)斷點(diǎn)
運(yùn)行
(Pdb)n #單步運(yùn)行
(Pdb)s #細(xì)點(diǎn)運(yùn)行 也就是會(huì)下到,方法
(Pdb)c #跳到下個(gè)斷點(diǎn)
查看
(Pdb)p param #查看當(dāng)前 變量值
(Pdb)l #查看運(yùn)行到某處代碼
(Pdb)a #查看全部棧內(nèi)變量
(Pdb)w 列出目前call stack 中的所在層。
(Pdb)d 在call stack中往下移一層
(Pdb)u 在call stack中往上移一層。如果在上移一層之后按下 n ,則會(huì)在上移之后的一層執(zhí)行下一個(gè)敘述,之前的 function call 就自動(dòng)返回。
(Pdb)cl 清除指定的斷點(diǎn)。如果沒有帶參數(shù),則清除所有斷點(diǎn)。
(Pdb)disable 取消所有斷點(diǎn)的功能,但仍然保留這些斷點(diǎn)。
(Pdb)enable 恢復(fù)斷點(diǎn)的功能。
(Pdb)ignore 設(shè)定斷點(diǎn)的忽略次數(shù)。如果沒指定 count,其初始 為 0。當(dāng) count 為 0 時(shí),斷點(diǎn)會(huì)正常動(dòng)作。若有指定 count,則每次執(zhí)行到該中斷, count 就少 1,直到 count 數(shù)為 0。
(Pdb)condition bpnumber [condition]
(Pdb)j(ump) lineNo. 跳到某行執(zhí)行。只有在 call stack 的最底部才能作用。
(Pdb)l 列出目前所在檔案中的位置。連續(xù)地 l 命令會(huì)一直列到檔案結(jié)尾,可以使用指定行數(shù)或范圍來打印。
(Pdb)pp 和 p 命令類似,但是使用 pprint module(沒用過 pprint,詳情請(qǐng)參考 Python Library Reference)。
(Pdb)alias 以一個(gè)"別名"代替"一群除錯(cuò)命令",有點(diǎn)類似 c/c 的 macro(詳情請(qǐng)參考 Python Library Reference)。
(Pdb)unalias 取消某個(gè) alias。
(Pdb)[!]statement 在目前的環(huán)境(context)中執(zhí)行敘述。
轉(zhuǎn)載
裝個(gè)Pycharm
1 添加斷點(diǎn)
2 Debug下運(yùn)行代碼:
3 F8:進(jìn)行下一步操作
F7 :跳入下一個(gè)方法中
單元測(cè)試(Unit Testing)
為程序編寫測(cè)試——如果做的到位——有助于減少bug的出現(xiàn),并可以提高我們對(duì)程序按預(yù)期目標(biāo)運(yùn)行的信心。通常,測(cè)試并不能保證正確性,因?yàn)閷?duì)大多數(shù)程序而言, 可能的輸入范圍以及可能的計(jì)算范圍是如此之大,只有其中最小的一部分能被實(shí)際地進(jìn) 行測(cè)試。盡管如此,通過仔細(xì)地選擇測(cè)試的方法和目標(biāo),可以提高代碼的質(zhì)量。
大量不同類型的測(cè)試都可以進(jìn)行,比如可用性測(cè)試、功能測(cè)試以及整合測(cè)試等。這里, 我們只講單元測(cè)試一對(duì)單獨(dú)的函數(shù)、類與方法進(jìn)行測(cè)試,確保其符合預(yù)期的行為。
TDD的一個(gè)關(guān)鍵點(diǎn)是,當(dāng)我們想添加一個(gè)功能時(shí)——比如為類添加一個(gè)方法—— 我們首次為其編寫一個(gè)測(cè)試用例。當(dāng)然,測(cè)試將失敗,因?yàn)槲覀冞€沒有實(shí)際編寫該方法。現(xiàn)在,我們編寫該方法,一旦方法通過了測(cè)試,就可以返回所有測(cè)試,確保我們新添加的代碼沒有任何預(yù)期外的副作用。一旦所有測(cè)試運(yùn)行完畢(包括我們?yōu)樾鹿δ芫帉懙臏y(cè)試),就可以對(duì)我們的代碼進(jìn)行檢查,并有理有據(jù)地相信程序行為符合我們的期望——當(dāng)然,前提是我們的測(cè)試是適當(dāng)?shù)摹?/p>
比如,我們編寫了一個(gè)函數(shù),該函數(shù)在特定的索引位置插入一個(gè)字符串,可以像下面這樣開始我們的TDD:
def insert_at(string, position, insert):
"""Returns a copy of string with insert inserted at the position
string = "ABCDE"
result =[]
for i in range(-2, len(string) + 2):
... result.append(insert_at(string, i,“-”))
result[:5]
['ABC-DE', 'ABCD-E', '-ABCDE','A-BCDE', 'AB-CDE']
result[5:]
['ABC-DE', 'ABCD-E', 'ABCDE-', 'ABCDE-']
"""
return string
對(duì)不返回任何參數(shù)的函數(shù)或方法(通常返回None),我們通常賦予其由pass構(gòu)成的一個(gè)suite,對(duì)那些返回值被試用的,我們或者返回一個(gè)常數(shù)(比如0),或者某個(gè)不變的參數(shù)——這也是我們這里所做的。(在更復(fù)雜的情況下,返回fake對(duì)象可能更有用一一對(duì)這樣的類,提供mock對(duì)象的第三方模塊是可用的。)
運(yùn)行doctest時(shí)會(huì)失敗,并列出每個(gè)預(yù)期內(nèi)的字符串('ABCD-EF'、'ABCDE-F' 等),及其實(shí)際獲取的字符串(所有的都是'ABCD-EF')。一旦確定doctest是充分的和正確的,就可以編寫該函數(shù)的主體部分,在本例中只是簡(jiǎn)單的return string[:position] + insert+string[position:]。(如果我們編寫的是 return string[:position] + insert,之后復(fù)制 string [:position]并將其粘貼在末尾以便減少一些輸入操作,那么doctest會(huì)立即提示錯(cuò)誤。)
Python的標(biāo)準(zhǔn)庫提供了兩個(gè)單元測(cè)試模塊,一個(gè)是doctest,這里和前面都簡(jiǎn)單地提到過,另一個(gè)是unittest。此外,還有一些可用于Python的第三方測(cè)試工具。其中最著名的兩個(gè)是nose (code.google.com/p/python-nose)與py.test (codespeak.net/py/dist/test/test.html), nose 致力于提供比標(biāo)準(zhǔn)的unittest 模塊更廣泛的功能,同時(shí)保持與該模塊的兼容性,py.test則采用了與unittest有些不同的方法,試圖盡可能消除樣板測(cè)試代碼。這兩個(gè)第三方模塊都支持測(cè)試發(fā)現(xiàn),因此沒必要寫一個(gè)總體的測(cè)試程序——因?yàn)槟K將自己搜索測(cè)試程序。這使得測(cè)試整個(gè)代碼樹或某一部分 (比如那些已經(jīng)起作用的模塊)變得很容易。那些對(duì)測(cè)試嚴(yán)重關(guān)切的人,在決定使用哪個(gè)測(cè)試工具之前,對(duì)這兩個(gè)(以及任何其他有吸引力的)第三方模塊進(jìn)行研究都是值 得的。
創(chuàng)建doctest是直截了當(dāng)?shù)模何覀冊(cè)谀K中編寫測(cè)試、函數(shù)、類與方法的docstrings。 對(duì)于模塊,我們簡(jiǎn)單地在末尾添加了 3行:
if __name__ =="__main__":
import doctest
doctest.testmod()
在程序內(nèi)部使用doctest也是可能的。比如,blocks.py程序(其模塊在后面)有自己函數(shù)的doctest,但以如下代碼結(jié)尾:
if __name__== "__main__":
main()
這里簡(jiǎn)單地調(diào)用了程序的main()函數(shù),并且沒有執(zhí)行程序的doctest。要實(shí)驗(yàn)程序的 doctest,有兩種方法。一種是導(dǎo)入doctest模塊,之后運(yùn)行程序---比如,在控制臺(tái)中輸 入 python3 -m doctest blocks.py (在 Wndows 平臺(tái)上,使用類似于 C:Python3 lpython.exe 這樣的形式替代python3)。如果所有測(cè)試運(yùn)行良好,就沒有輸出,因此,我們可能寧愿執(zhí)行python3-m doctest blocks.py-v,因?yàn)檫@會(huì)列出每個(gè)執(zhí)行的doctest,并在最后給出結(jié)果摘要。
另一種執(zhí)行doctest的方法是使用unittest模塊創(chuàng)建單獨(dú)的測(cè)試程序。在概念上, unittest模塊是根據(jù)Java的JUnit單元測(cè)試庫進(jìn)行建模的,并用于創(chuàng)建包含測(cè)試用例的測(cè)試套件。unittest模塊可以基于doctests創(chuàng)建測(cè)試用例,而不需要知道程序或模塊包含的任何事物——只要知道其包含doctest即可。因此,為給blocks.py程序制作一個(gè)測(cè)試套件,我們可以創(chuàng)建如下的簡(jiǎn)單程序(將其稱為test_blocks.py):
import doctest
import unittest
import blocks
suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite(blocks))
runner = unittest.TextTestRunner()
print(runner.run(suite))
注意,如果釆用這種方法,程序的名稱上會(huì)有一個(gè)隱含的約束:程序名必須是有效的模塊名。因此,名為convert-incidents.py的程序的測(cè)試不能寫成這樣。因?yàn)閕mport convert-incidents不是有效的,在Python標(biāo)識(shí)符中,連接符是無效的(避開這一約束是可能的,但最簡(jiǎn)單的解決方案是使用總是有效模塊名的程序文件名,比如,使用下劃線替換連接符)。這里展示的結(jié)構(gòu)(創(chuàng)建一個(gè)測(cè)試套件,添加一個(gè)或多個(gè)測(cè)試用例或測(cè)試套件,運(yùn)行總體的測(cè)試套件,輸出結(jié)果)是典型的機(jī)遇unittest的測(cè)試。運(yùn)行時(shí),這一特定實(shí)例產(chǎn)生如下結(jié)果:
...
.............................................................................................................
Ran 3 tests in 0.244s
OK
每次執(zhí)行一個(gè)測(cè)試用例時(shí),都會(huì)輸出一個(gè)句點(diǎn)(因此上面的輸出最前面有3個(gè)句點(diǎn)),之后是一行連接符,再之后是測(cè)試摘要(如果有任何一個(gè)測(cè)試失敗,就會(huì)有更多的輸出信息)。
如果我們嘗試將測(cè)試分離開(典型情況下是要測(cè)試的每個(gè)程序和模塊都有一個(gè)測(cè)試用例),就不要再使用doctests,而是直接使用unittest模塊的功能——尤其是我們習(xí)慣于使用JUnit方法進(jìn)行測(cè)試時(shí)ounittest模塊會(huì)將測(cè)試分離于代碼——對(duì)大型項(xiàng)目(測(cè)試編寫人員與開發(fā)人員可能不一致)而言,這種方法特別有用。此外,unittest單元測(cè)試編寫為獨(dú)立的Python模塊,因此,不會(huì)像在docstring內(nèi)部編寫測(cè)試用例時(shí)受到兼容性和明智性的限制。
unittest模塊定義了 4個(gè)關(guān)鍵概念。測(cè)試夾具是一個(gè)用于描述創(chuàng)建測(cè)試(以及用完之后將其清理)所必需的代碼的術(shù)語,典型實(shí)例是創(chuàng)建測(cè)試所用的一個(gè)輸入文件,最后刪除輸入文件與結(jié)果輸出文件。測(cè)試套件是一組測(cè)試用例的組合。測(cè)試用例是測(cè)試的基本單元—我們很快就會(huì)看到實(shí)例。測(cè)試運(yùn)行者是執(zhí)行一個(gè)或多個(gè)測(cè)試套件的對(duì)象。
典型情況下,測(cè)試套件是通過創(chuàng)建unittest.TestCase的子類實(shí)現(xiàn)的,其中每個(gè)名稱 以“test”開頭的方法都是一個(gè)測(cè)試用例。如果我們需要完成任何創(chuàng)建操作,就可以在一個(gè)名為setUp()的方法中實(shí)現(xiàn);類似地,對(duì)任何清理操作,也可以實(shí)現(xiàn)一個(gè)名為 tearDown()的方法。在測(cè)試內(nèi)部,有大量可供我們使用的unittest.TestCase方法,包括 assertTrue()、assertEqual()、assertAlmostEqual()(對(duì)于測(cè)試浮點(diǎn)數(shù)很有用)、assertRaises() 以及更多,還包括很多對(duì)應(yīng)的逆方法,比如assertFalse()、assertNotEqual()、failIfEqual()、 failUnlessEqual ()等。
unittest模塊進(jìn)行了很好的歸檔,并且提供了大量功能,但在這里我們只是通過一 個(gè)非常簡(jiǎn)單的測(cè)試套件來感受一下該模塊的使用。這里將要使用的實(shí)例,該練習(xí)要求創(chuàng)建一個(gè)Atomic模塊,該模塊可以用作一 個(gè)上下文管理器,以確保或者所有改變都應(yīng)用于某個(gè)列表、集合或字典,或者所有改變都不應(yīng)用。作為解決方案提供的Atomic.py模塊使用30行代碼來實(shí)現(xiàn)Atomic類, 并提供了 100行左右的模塊doctest。這里,我們將創(chuàng)建test_Atomic.py模塊,并使用 unittest測(cè)試替換doctest,以便可以刪除doctest。
在編寫測(cè)試模塊之前,我們需要思考都需要哪些測(cè)試。我們需要測(cè)試3種不同的數(shù)據(jù)類型:列表、集合與字典。對(duì)于列表,需要測(cè)試的是插入項(xiàng)、刪除項(xiàng)或修改項(xiàng)的值。對(duì)于集合,我們必須測(cè)試向其中添加或刪除一個(gè)項(xiàng)。對(duì)于字典,我們必須測(cè)試的是插入一個(gè)項(xiàng)、修改一個(gè)項(xiàng)的值、刪除一個(gè)項(xiàng)。此外,還必須要測(cè)試的是在失敗的情況下,不會(huì)有任何改變實(shí)際生效。
結(jié)構(gòu)上看,測(cè)試不同數(shù)據(jù)類型實(shí)質(zhì)上是一樣的,因此,我們將只為測(cè)試列表編寫測(cè)試用例,而將其他的留作練習(xí)。test_Atomic.py模塊必須導(dǎo)入unittest模塊與要進(jìn)行測(cè)試的Atomic模塊。
創(chuàng)建unittest文件時(shí),我們通常創(chuàng)建的是模塊而非程序。在每個(gè)模塊內(nèi)部,我們定義一個(gè)或多個(gè)unittest.TestCase子類。比如,test_Atomic.py模塊中僅一個(gè)單獨(dú)的 unittest-TestCase子類,也就是TestAtomic (稍后將對(duì)其進(jìn)行講解),并以如下兩行結(jié)束:
if name == "__main__":
unittest.main()
這兩行使得該模塊可以單獨(dú)運(yùn)行。當(dāng)然,該模塊也可以被導(dǎo)入并從其他測(cè)試程序中運(yùn)行——如果這只是多個(gè)測(cè)試套件中的一個(gè),這一點(diǎn)是有意義的。
如果想要從其他測(cè)試程序中運(yùn)行test_Atomic.py模塊,那么可以編寫一個(gè)與此類似的程序。我們習(xí)慣于使用unittest模塊執(zhí)行doctests,比如:
import unittest
import test_Atomic
suite = unittest.TestLoader().loadTestsFromTestCase(test_Atomic.TestAtomic)
runner = unittest.TextTestRunner()
pnnt(runner.run(suite))
這里,我們已經(jīng)創(chuàng)建了一個(gè)單獨(dú)的套件,這是通過讓unittest模塊讀取test_Atomic 模塊實(shí)現(xiàn)的,并且使用其每一個(gè)test*()方法(本實(shí)例中是test_list_success()、test_list_fail(),稍后很快就會(huì)看到)作為測(cè)試用例。
我們現(xiàn)在將查看TestAtomic類的實(shí)現(xiàn)。對(duì)通常的子類(不包括unittest.TestCase 子類),不怎么常見的是,沒有必要實(shí)現(xiàn)初始化程序。在這一案例中,我們將需要建立 一個(gè)方法,但不需要清理方法,并且我們將實(shí)現(xiàn)兩個(gè)測(cè)試用例。
def setUp(self):
self.original_list = list(range(10))
我們已經(jīng)使用了 unittest.TestCase.setUp()方法來創(chuàng)建單獨(dú)的測(cè)試數(shù)據(jù)片段。
def test_list_succeed(self):
items = self.original_list[:]
with Atomic.Atomic(items) as atomic:
atomic.append(1999)
atomic.insert(2, -915)
del atomic[5]
atomic[4]= -782
atomic.insert(0, -9)
self.assertEqual(items,
[-9, 0, 1, -915, 2, -782, 5, 6, 7, 8, 9, 1999])
def test_list_fail(self):
items = self.original_list[:]
with self.assertRaises(AttributeError):
with Atomic.Atomic(items) as atomic:
atomic.append(1999)
atomic.insert(2, -915)
del atomic[5]
atomic[4] = -782
atomic.poop() # Typo
self.assertListEqual(items, self.original_list)
這里,我們直接在測(cè)試方法中編寫了測(cè)試代碼,而不需要一個(gè)內(nèi)部函數(shù),也不再使用unittest.TestCase.assertRaised()作為上下文管理器(期望代碼產(chǎn)生AttributeError)。 最后我們也使用了 Python 3.1 的 unittest.TestCase.assertListEqual()方法。
正如我們已經(jīng)看到的,Python的測(cè)試模塊易于使用,并且極為有用,在我們使用 TDD的情況下更是如此。它們還有比這里展示的要多得多的大量功能與特征——比如,跳過測(cè)試的能力,這有助于理解平臺(tái)差別——并且這些都有很好的文檔支持。缺失的一個(gè)功能——但nose與py.test提供了——是測(cè)試發(fā)現(xiàn),盡管這一特征被期望在后續(xù)的Python版本(或許與Python 3.2—起)中出現(xiàn)。
性能剖析(Profiling)
如果程序運(yùn)行很慢,或者消耗了比預(yù)期內(nèi)要多得多的內(nèi)存,那么問題通常是選擇的算法或數(shù)據(jù)結(jié)構(gòu)不合適,或者是以低效的方式進(jìn)行實(shí)現(xiàn)。不管問題的原因是什么, 最好的方法都是準(zhǔn)確地找到問題發(fā)生的地方,而不只是檢査代碼并試圖對(duì)其進(jìn)行優(yōu)化。 隨機(jī)優(yōu)化會(huì)導(dǎo)致引入bug,或者對(duì)程序中本來對(duì)程序整體性能并沒有實(shí)際影響的部分進(jìn)行提速,而這并非解釋器耗費(fèi)大部分時(shí)間的地方。
在深入討論profiling之前,注意一些易于學(xué)習(xí)和使用的Python程序設(shè)計(jì)習(xí)慣是有意義的,并且對(duì)提高程序性能不無裨益。這些技術(shù)都不是特定于某個(gè)Python版本的, 而是合理的Python程序設(shè)計(jì)風(fēng)格。第一,在需要只讀序列時(shí),最好使用元組而非列表; 第二,使用生成器,而不是創(chuàng)建大的元組和列表并在其上進(jìn)行迭代處理;第三,盡量使用Python內(nèi)置的數(shù)據(jù)結(jié)構(gòu) dicts、lists、tuples 而不實(shí)現(xiàn)自己的自定義結(jié)構(gòu),因?yàn)閮?nèi)置的數(shù)據(jù)結(jié)構(gòu)都是經(jīng)過了高度優(yōu)化的;第四,從小字符串中產(chǎn)生大字符串時(shí), 不要對(duì)小字符串進(jìn)行連接,而是在列表中累積,最后將字符串列表結(jié)合成為一個(gè)單獨(dú)的字符串;第五,也是最后一點(diǎn),如果某個(gè)對(duì)象(包括函數(shù)或方法)需要多次使用屬性進(jìn)行訪問(比如訪問模塊中的某個(gè)函數(shù)),或從某個(gè)數(shù)據(jù)結(jié)構(gòu)中進(jìn)行訪問,那么較好的做法是創(chuàng)建并使用一個(gè)局部變量來訪問該對(duì)象,以便提供更快的訪問速度。
Python標(biāo)準(zhǔn)庫提供了兩個(gè)特別有用的模塊,可以輔助調(diào)査代碼的性能問題。一個(gè)是timeit模塊——該模塊可用于對(duì)一小段Python代碼進(jìn)行計(jì)時(shí),并可用于諸如對(duì)兩個(gè)或多個(gè)特定函數(shù)或方法的性能進(jìn)行比較等場(chǎng)合。另一個(gè)是cProfile模塊,可用于profile 程序的性能——該模塊對(duì)調(diào)用計(jì)數(shù)與次數(shù)進(jìn)行了詳細(xì)分解,以便發(fā)現(xiàn)性能瓶頸所在。
為了解timeit模塊,我們將查看一些小實(shí)例。假定有3個(gè)函數(shù)function_a()、 function_b()、function_c(), 3個(gè)函數(shù)執(zhí)行同樣的計(jì)算,但分別使用不同的算法。如果將這些函數(shù)放于同一個(gè)模塊中(或分別導(dǎo)入),就可以使用timeit模塊對(duì)其進(jìn)行運(yùn)行和比較。下面給出的是模塊最后使用的代碼:
if __name__ == "__main__":
repeats = 1000
for function in ("function_a", "function_b", "function_c"):
t = timeit.Timer("{0}(X, Y)".format(function),"from __main__ import {0}, X, Y".format(function))
sec = t.timeit(repeats) / repeats
print("{function}() {sec:.6f} sec".format(**locals()))
賦予timeit.Timer()構(gòu)造子的第一個(gè)參數(shù)是我們想要執(zhí)行并計(jì)時(shí)的代碼,其形式是字符串。這里,該字符串是“function_a(X,Y)”;第二個(gè)參數(shù)是可選的,還是一個(gè)待執(zhí)行的字符串,這一次是在待計(jì)時(shí)的代碼之前,以便提供一些建立工作。這里,我們從 __main__ (即this)模塊導(dǎo)入了待測(cè)試的函數(shù),還有兩個(gè)作為輸入數(shù)據(jù)傳入的變量(X 與Y),這兩個(gè)變量在該模塊中是作為全局變量提供的。我們也可以很輕易地像從其他模塊中導(dǎo)入數(shù)據(jù)一樣來進(jìn)行導(dǎo)入操作。
調(diào)用timeit.Timer對(duì)象的timeit()方法時(shí),首先將執(zhí)行構(gòu)造子的第二個(gè)參數(shù)(如果有), 之后執(zhí)行構(gòu)造子的第一個(gè)參數(shù)并對(duì)其執(zhí)行時(shí)間進(jìn)行計(jì)時(shí)。timeit.Timer.timeit()方法的返回值是以秒計(jì)數(shù)的時(shí)間,類型是float。默認(rèn)情況下,timeit()方法重復(fù)100萬次,并返回所 有這些執(zhí)行的總秒數(shù),但在這一特定案例中,只需要1000次反復(fù)就可以給出有用的結(jié)果, 因此對(duì)重復(fù)計(jì)數(shù)次數(shù)進(jìn)行了顯式指定。在對(duì)每個(gè)函數(shù)進(jìn)行計(jì)時(shí)后,使用重復(fù)次數(shù)對(duì)總數(shù)進(jìn)行除法操作,就得到了平均執(zhí)行時(shí)間,并在控制臺(tái)中打印出函數(shù)名與執(zhí)行時(shí)間。
function_a() 0.001618 sec
function_b() 0.012786 sec
function_c() 0.003248 sec
在這一實(shí)例中,function_a()顯然是最快的——至少對(duì)于這里使用的輸入數(shù)據(jù)而言。 在有些情況下一一比如輸入數(shù)據(jù)不同會(huì)對(duì)性能產(chǎn)生巨大影響——可能需要使用多組輸入數(shù)據(jù)對(duì)每個(gè)函數(shù)進(jìn)行測(cè)試,以便覆蓋有代表性的測(cè)試用例,并對(duì)總執(zhí)行時(shí)間或平均執(zhí)行時(shí)間進(jìn)行比較。
有時(shí)監(jiān)控自己的代碼進(jìn)行計(jì)時(shí)并不是很方便,因此timeit模塊提供了一種在命令行中對(duì)代碼執(zhí)行時(shí)間進(jìn)行計(jì)時(shí)的途徑。比如,要對(duì)MyModule.py模塊中的函數(shù)function_a()進(jìn)行計(jì)時(shí),可以在控制臺(tái)中輸入如下命令:python3 -m timeit -n 1000 -s "from MyModule import function_a, X, Y" "function_a(X, Y)"(與通常所做的一樣,對(duì) Windows 環(huán)境,我們必須使用類似于C:Python3lpython.exe這樣的內(nèi)容來替換python3)。-m選項(xiàng)用于Python 解釋器,使其可以加載指定的模塊(這里是timeit),其他選項(xiàng)則由timeit模塊進(jìn)行處理。 -n選項(xiàng)指定了循環(huán)計(jì)數(shù)次數(shù),-s選項(xiàng)指定了要建立,最后一個(gè)參數(shù)是要執(zhí)行和計(jì)時(shí)的代碼。命令完成后,會(huì)向控制臺(tái)中打印運(yùn)行結(jié)果,比如:
1000 loops, best of 3: 1.41 msec per loop
之后我們可以輕易地對(duì)其他兩個(gè)函數(shù)進(jìn)行計(jì)時(shí),以便對(duì)其進(jìn)行整體的比較。
cProfile模塊(或者profile模塊,這里統(tǒng)稱為cProfile模塊)也可以用于比較函數(shù) 與方法的性能。與只是提供原始計(jì)時(shí)的timeit模塊不同的是,cProfile模塊精確地展示 了有什么被調(diào)用以及每個(gè)調(diào)用耗費(fèi)了多少時(shí)間。下面是用于比較與前面一樣的3個(gè)函數(shù)的代碼:
if __name__ == "__main__":
for function in ("function_a", "function_b", "function_c"):
cProfile.run("for i in ranged 1000): {0}(X, Y)".format(function))
我們必須將重復(fù)的次數(shù)放置在要傳遞給cProfile.run()函數(shù)的代碼內(nèi)部,但不需要做任何創(chuàng)建,因?yàn)槟K函數(shù)會(huì)使用內(nèi)省來尋找需要使用的函數(shù)與變量。這里沒有使用顯式的print()語句,因?yàn)槟J(rèn)情況下,cProfile.run()函數(shù)會(huì)在控制臺(tái)中打印其輸出。下面給出的是所有函數(shù)的相關(guān)結(jié)果(有些無關(guān)行被省略,格式也進(jìn)行了稍許調(diào)整,以便與頁面適應(yīng)):
1003 function calls in 1.661 CPU seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.003 0.003 1.661 1.661 :1 ( )
1000 1.658 0.002 1.658 0.002 MyModule.py:21 (function_a)
1 0.000 0.000 1.661 1.661 {built-in method exec}
5132003 function calls in 22.700 CPU seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.487 0.487 22.700 22.700 : 1 ( )
1000 0.011 0.000 22.213 0.022 MyModule.py:28(function_b)
5128000 7.048 0.000 7.048 0.000 MyModule.py:29( )
1000 0.00 50.000 0.005 0.000 {built-in method bisectjeft}
1 0.000 0.000 22.700 22.700 {built-in method exec}
1000 0.001 0.000 0.001 0.000 {built-in method len}
1000 15.149 0.015 22.196 0.022 {built-in method sorted}
5129003 function calls in 12.987 CPU seconds
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.205 0.205 12.987 12.987 :l ( )
1000 6.472 0.006 12.782 0.013 MyModule.py:36(function_c)
5128000 6.311 0.000 6.311 0.000 MyModule.py:37( )
1 0.000 0.000 12.987 12.987 {built-in method exec}
ncalls ("調(diào)用的次數(shù)")列列出了對(duì)指定函數(shù)(在filename:lineno(function)中列出) 的調(diào)用次數(shù)。回想一下我們重復(fù)了 1000次調(diào)用,因此必須將這個(gè)次數(shù)記住。tottime (“總的時(shí)間”)列列出了某個(gè)函數(shù)中耗費(fèi)的總時(shí)間,但是排除了函數(shù)調(diào)用的其他函數(shù)內(nèi)部花費(fèi)的時(shí)間。第一個(gè)percall列列出了對(duì)函數(shù)的每次調(diào)用的平均時(shí)間(tottime // ncalls)。 cumtime ("累積時(shí)間")列出了在函數(shù)中耗費(fèi)的時(shí)間,并且包含了函數(shù)調(diào)用的其他函數(shù)內(nèi)部花費(fèi)的時(shí)間。第二個(gè)percall列列出了對(duì)函數(shù)的每次調(diào)用的平均時(shí)間,包括其調(diào)用的函數(shù)耗費(fèi)的時(shí)間。
這種輸出信息要比timeit模塊的原始計(jì)時(shí)信息富有啟發(fā)意義的多。我們立即可以發(fā)現(xiàn),function_b()與function_c()使用了被調(diào)用5000次以上的生成器,使得它們的速度至少要比function_a()慢10倍以上。并且,function_b()調(diào)用了更多通常意義上的函數(shù),包括調(diào)用內(nèi)置的sorted()函數(shù),這使得其幾乎比function_c()還要慢兩倍。當(dāng)然,timeit() 模塊提供了足夠的信息來查看計(jì)時(shí)上存在的這些差別,但cProfile模塊允許我們了解為什么會(huì)存在這些差別。正如timeit模塊允許對(duì)代碼進(jìn)行計(jì)時(shí)而又不需要對(duì)其監(jiān)控一樣,cProfile模塊也可以做到這一點(diǎn)。然而,從命令行使用cProfile模塊時(shí),我們不能精確地指定要執(zhí)行的 是什么——而只是執(zhí)行給定的程序或模塊,并報(bào)告所有這些的計(jì)時(shí)結(jié)果。需要使用的 命令行是python3 -m cProfile programOrModule.py,產(chǎn)生的輸出信息與前面看到的一 樣,下面給出的是輸出信息樣例,格式上進(jìn)行了一些調(diào)整,并忽略了大多數(shù)行:
10272458 function calls (10272457 primitive calls) in 37.718 CPU secs
ncalls tottime percall cumtime percall filename:lineno(function)
10.000 0.000 37.718 37.718 :1 ( )
10.719 0.719 37.717 37.717 :12( )
1000 1.569 0.002 1.569 0.002 :20(function_a)
1000 0.011 0.000 22.560 0.023 :27(function_b)
5128000 7.078 0.000 7.078 0.000 :28( )
1000 6.510 0.007 12.825 0.013 :35(function_c)
5128000 6.316 0.000 6.316 0.000 :36( )
在cProfile術(shù)語學(xué)中,原始調(diào)用指的就是非遞歸的函數(shù)調(diào)用。
以這種方式使用cProfile模塊對(duì)于識(shí)別值得進(jìn)一步研究的區(qū)域是有用的。比如,這里 我們可以清晰地看到function_b()需要耗費(fèi)更長(zhǎng)的時(shí)間,但是我們?cè)鯓荧@取進(jìn)一步的詳細(xì)資料?我們可以使用cProfile.run("function_b()")來替換對(duì)function_b()的調(diào)用。或者可以保存完全的profile數(shù)據(jù)并使用pstats模塊對(duì)其進(jìn)行分析。要保存profile,就必須對(duì)命令行進(jìn)行稍許修改:python3 -m cProfile -o profileDataFile programOrModule.py。 之后可以對(duì) profile 數(shù)據(jù)進(jìn)行分析,比如啟動(dòng)IDLE,導(dǎo)入pstats模塊,賦予其已保存的profileDataFile,或者也可以在控制臺(tái)中交互式地使用pstats。
下面給出的是一個(gè)非常短的控制臺(tái)會(huì)話實(shí)例,為使其適合頁面展示,進(jìn)行了適當(dāng)調(diào)整,我們自己的輸入則以粗體展示:
$ python3 -m cProfile -o profile.dat MyModule.py
$ python3 -m pstats
Welcome to the profile statistics browser.
% read profile.dat
profile.dat% callers function_b
Random listing order was used
List reduced from 44 to 1 due to restriction
Function was called by...
ncalls tottime cumtime
:27(function_b) - 1000 0.011 22.251 :12( )
profile.dat% callees function_b
Random listing order was used
List reduced from 44 to 1 due to restriction
Function called...
ncalls tottime cumtime
:27(function_b)-
1000 0.005 0.005 built-in method bisectJeft
1000 0.001 0.001 built-in method len
1000 1 5.297 22.234 built-in method sorted
profile.dat% quit
輸入help可以獲取命令列表,help后面跟隨命令名可以獲取該命令的更多信息。比如, help stats將列出可以賦予stats命令的參數(shù)。還有其他一些可用的工具,可以提供profile數(shù)據(jù)的圖形化展示形式,比如 RunSnakeRun (), 該工具需要依賴于wxPython GUI庫。
使用timeit與cProfile模塊,我們可以識(shí)別出我們自己代碼中哪些區(qū)域會(huì)耗費(fèi)超過預(yù)期的時(shí)間;使用cProfile模塊,還可以準(zhǔn)確算岀時(shí)間消耗在哪里。
以上內(nèi)容部分摘自視頻課程 05后端編程Python-19調(diào)試、測(cè)試和性能調(diào)優(yōu)(下) ,更多實(shí)操示例請(qǐng)參照視頻講解。跟著張員外講編程,學(xué)習(xí)更輕松,不花錢還能學(xué)習(xí)真本領(lǐng)。
目錄
許多編程語言都有一個(gè)特殊的函數(shù),當(dāng)操作系統(tǒng)開始運(yùn)行程序時(shí)會(huì)自動(dòng)執(zhí)行該函數(shù)。這個(gè)函數(shù)通常被命名為main(),并且依據(jù)語言標(biāo)準(zhǔn)具有特定的返回類型和參數(shù)。另一方面,Python解釋器從文件頂部開始執(zhí)行腳本,并且沒有自動(dòng)執(zhí)行的特殊函數(shù)。
盡管如此,為程序的執(zhí)行定義一個(gè)起始點(diǎn)有助于理解程序是如何運(yùn)行的。Python程序員提出了幾種方式對(duì)此進(jìn)行實(shí)現(xiàn)。
本文結(jié)束時(shí),您將了解以下內(nèi)容:
Python中的基本main()函數(shù)
一些Python腳本中,包含一個(gè)函數(shù)定義和一個(gè)條件語句,如下所示:
此代碼中,包含一個(gè)main()函數(shù),在程序執(zhí)行時(shí)打印Hello World!。此外,還包含一個(gè)條件(或if)語句,用于檢查__name__的值并將其與字符串"__main__"進(jìn)行比較。當(dāng)if語句為True時(shí),Python解釋器將執(zhí)行main()函數(shù)。更多關(guān)于Python條件語句的信息可以由此獲得。
這種代碼模式在Python文件中非常常見,它將作為腳本執(zhí)行并導(dǎo)入另一個(gè)模塊。為了幫助理解這段代碼的執(zhí)行方式,首先需要了解Python解釋器如何根據(jù)代碼的執(zhí)行方式設(shè)置__name__。
Python中的執(zhí)行模式
Python解釋器執(zhí)行代碼有兩種方式:
更多內(nèi)容可參考如何運(yùn)行Python腳本。無論采用哪種方式,Python都會(huì)定義一個(gè)名為__name__的特殊變量,該變量包含一個(gè)字符串,其值取決于代碼的使用方式。
本文將如下示例文件保存為execution_methods.py,以 探索 代碼如何根據(jù)上下文改變行為:
在此文件中,定義了三個(gè)對(duì)print()函數(shù)的調(diào)用。前兩個(gè)打印一些介紹性短語。第三個(gè)print()會(huì)先打印短語The value __name__ is,之后將使用Python內(nèi)置的repr()函數(shù)打印出__name__變量。
在Python中,repr()函數(shù)將對(duì)象轉(zhuǎn)化為供解釋器讀取的形式。上述示例通過使用repr()函數(shù)來強(qiáng)調(diào)__name__的值為字符串。更多關(guān)于repr()的內(nèi)容可參考Python文檔。
在本文中,您將隨處可見文件(file),模塊(module)和腳本(script)這三個(gè)字眼。實(shí)際上,三者之間并無太大的差別。不過,在強(qiáng)調(diào)代碼目的時(shí),還是存在細(xì)微的差異:
“如何運(yùn)行Python腳本”一文也討論了三者的差別。
基于命令行執(zhí)行
在這類方法中,Python腳本將通過命令行來執(zhí)行。
執(zhí)行腳本時(shí),無法與Python解釋器正在執(zhí)行的代碼交互。關(guān)于如何通過命令行執(zhí)行代碼的詳細(xì)信息對(duì)本文而言并不重要,但您可以通過展開下框閱讀更多有關(guān)Windows,Linux和macOS之間命令行差異的內(nèi)容。
命令行環(huán)境
不同的操作系統(tǒng)在使用命令行執(zhí)行代碼時(shí)存在細(xì)微的差異。
在Linux和macOS中,通常使用如下命令:
美元符號(hào)($)之前的內(nèi)容可能有所不同,具體取決于您的用戶名和計(jì)算機(jī)名稱。您鍵入的命令位于$之后。在Linux或macOS上,Python3的可執(zhí)行文件名為python3,因此可以通過輸入python3 script_name.py來運(yùn)行python腳本。
在Windows上,命令提示符通常如下所示:
根據(jù)您的用戶名,之前的內(nèi)容可能會(huì)有所不同,您輸入的命令位于之后。在Windows上,Python3的可執(zhí)行文件通常為python。因此可以通過輸入python script_name.py來運(yùn)行python腳本。
無論哪種操作系統(tǒng),本文的Python腳本的輸出結(jié)果都是相同的。因此本文以Linux和macOS為例。
使用命令行執(zhí)行execution_methods.py,如下所示:
在這個(gè)示例中,__name__具有值'__main__',其中引號(hào)(')表明該值為字符串類型。
請(qǐng)記住,在Python中,使用單引號(hào)(')和雙引號(hào)(")定義的字符串沒有區(qū)別。更多關(guān)于字符串的內(nèi)容請(qǐng)參考Python的基本數(shù)據(jù)類型。
如果在腳本中包含"shebang行"并直接執(zhí)行它(./execution_methods.py),或者使用IPython或Jupyter Notebook的%run,將會(huì)獲取相同的結(jié)果。
您還可以通過向命令行添加-m參數(shù)的方法實(shí)現(xiàn)以模塊的方式執(zhí)行。通常情況下,推薦如下方式pip: python3 -m pip install package_name。
添加-m參數(shù)將會(huì)運(yùn)行包中__main__.py的代碼。更多關(guān)于__main__.py文件的內(nèi)容可參考如何將開源Python包發(fā)布到PyPI中。
在三種情況中,__name__都具有相同的值:字符串'__main__'。
技術(shù)細(xì)節(jié):Python文檔中具體定義了__name__何時(shí)取值為'__main__'。
當(dāng)通過標(biāo)準(zhǔn)輸入,腳本或者交互提示中讀取數(shù)據(jù)時(shí),模塊的__name__將取值為'__main__'。(來源)
__name__與__doc__,__package__和其他屬性一起存儲(chǔ)在模塊的全局命名空間。更多關(guān)于屬性的信息可參考Python數(shù)據(jù)模型文檔,特別是關(guān)于模塊和包的信息,請(qǐng)參閱Python Import文檔。
導(dǎo)入模塊或解釋器
接下來是Python解釋器執(zhí)行代碼的第二種方式:導(dǎo)入。在開發(fā)模塊或腳本時(shí),可以使用import關(guān)鍵字導(dǎo)入他人已經(jīng)構(gòu)建的模塊。
在導(dǎo)入過程中,Python執(zhí)行指定模塊中定義的語句(但僅在第一次導(dǎo)入模塊時(shí))。要演示導(dǎo)入execution_methods.py文件的結(jié)果,需要啟動(dòng)Python解釋器,然后導(dǎo)入execution_methods.py文件:
在此代碼輸出中,Python解釋器執(zhí)行了三次print()函數(shù)調(diào)用。前兩行由于沒有變量,在輸出方面與在命令行上作為腳本執(zhí)行時(shí)完全相同。但是第三個(gè)輸出存在差異。
當(dāng)Python解釋器導(dǎo)入代碼時(shí),__name__的值與要導(dǎo)入的模塊的名稱相同。您可以通過第三行的輸出了解這一點(diǎn)。__name__的值為'execution_methods',是Python導(dǎo)入的.py文件。
注意如果您在沒有退出Python時(shí)再次導(dǎo)入模塊,將不會(huì)有輸出。
注意:更多關(guān)于導(dǎo)入在Python中如何工作的內(nèi)容請(qǐng)參考官方文檔和Python中的絕對(duì)和相對(duì)導(dǎo)入。
Main函數(shù)的最佳實(shí)踐
既然您已經(jīng)了解兩種執(zhí)行方式上的差異,那么掌握一些最佳實(shí)踐方案還是很有用的。它們將適用于編寫作為腳本運(yùn)行的代碼或者在另一個(gè)模塊導(dǎo)入的代碼。
如下是四種實(shí)踐方式:
將大部分代碼放入函數(shù)或類中
請(qǐng)記住,Python解釋器在導(dǎo)入模塊時(shí)會(huì)執(zhí)行模塊中的所有代碼。有時(shí)如果想要實(shí)現(xiàn)用戶可控的代碼,會(huì)導(dǎo)致一些副作用,例如:
在這種情況下,想要實(shí)現(xiàn)用戶控制觸發(fā)此代碼的執(zhí)行,而不是讓Python解釋器在導(dǎo)入模塊時(shí)執(zhí)行代碼。
因此,最佳方法是將大部分代碼包含在函數(shù)或類中。這是因?yàn)楫?dāng)Python解釋器遇到def或class關(guān)鍵字時(shí),它只存儲(chǔ)這些定義供以后使用,并且在用戶通知之前不會(huì)實(shí)際執(zhí)行。
將如下代碼保存在best_practices.py以證明這個(gè)想法:
在此代碼中,首先從time模塊中導(dǎo)入sleep()。
在這個(gè)示例中,參數(shù)以秒的形式傳入sleep()函數(shù)中,解釋器將暫停一段時(shí)間再運(yùn)行。隨后,使用print()函數(shù)打印關(guān)于代碼描述的語句。
之后,定義一個(gè)process_data()函數(shù),執(zhí)行如下五項(xiàng)操作:
在命令行中執(zhí)行
當(dāng)你將此文件作為腳本用命令行執(zhí)行時(shí)會(huì)發(fā)生什么呢?
Python解釋器將執(zhí)行函數(shù)定義之外的from time import sleep和print(),之后將創(chuàng)建函數(shù)process_data()。然后,腳本將退出而不做任何進(jìn)一步的操作,因?yàn)槟_本沒有任何執(zhí)行process_data()的代碼。
如下是這段腳本的執(zhí)行結(jié)果:
我們?cè)谶@里看到的輸出是第一個(gè)print()的結(jié)果。注意,從time導(dǎo)入和定義process_data()函數(shù)不產(chǎn)生結(jié)果。具體來說,調(diào)用定義在process_data()內(nèi)部的print()不會(huì)打印結(jié)果。
導(dǎo)入模塊或解釋器執(zhí)行
在會(huì)話(或其他模塊)中導(dǎo)入此文件時(shí),Python解釋器將執(zhí)行相同的步驟。
Python解釋器導(dǎo)入文件后,您可以使用已導(dǎo)入模塊中定義的任何變量,類或函數(shù)。為了證明這一點(diǎn),我們將使用可交互的Python解釋器。啟動(dòng)解釋器,然后鍵入import best_practices:
導(dǎo)入best_practices.py后唯一的輸出來自process_data()函數(shù)外定義的print()。導(dǎo)入模塊或解釋器執(zhí)行與基于命令行執(zhí)行類似。
使用__name__控制代碼的執(zhí)行
如何實(shí)現(xiàn)基于命令行而不使用Python解釋器導(dǎo)入文件來執(zhí)行呢?
您可以使用__name__來決定執(zhí)行上下文,并且當(dāng)__name__等于"__main__"時(shí)才執(zhí)行process_data()。在best_practices.py文件中添加如下代碼:
這段代碼添加了一個(gè)條件語句來檢驗(yàn)__name__的值。當(dāng)值為"__main__"時(shí),條件為True。記住當(dāng)__name__變量的特殊值為"__main__"時(shí)意味著Python解釋器會(huì)執(zhí)行腳本而不是將其導(dǎo)入。
條件語塊內(nèi)添加了四行代碼(第12,13,14和15行):
現(xiàn)在,在命令行中運(yùn)行best_practices.py,并觀察輸出的變化:
首先,輸出顯示了process_data()函數(shù)外的print()的調(diào)用結(jié)果。
之后,data的值被打印。因?yàn)楫?dāng)Python解釋器將文件作為腳本執(zhí)行時(shí),變量__name__具有值"__main__",因此條件語句被計(jì)算為True。
接下來,腳本將調(diào)用process_data()并傳入data進(jìn)行修改。當(dāng)process_data執(zhí)行時(shí),將輸出一些狀態(tài)信息。最終,將輸出modified_data的值。
現(xiàn)在您可以驗(yàn)證從解釋器(或其他模塊)導(dǎo)入best_practices.py后發(fā)生的事情了。如下示例演示了這種情況:
注意,當(dāng)前結(jié)果與將條件語句添加到文件末尾之前相同。因?yàn)榇藭r(shí)__name__變量的值為"best_practices",因此條件語句結(jié)果為False,Python將不執(zhí)行process_data()。
創(chuàng)建名為main()的函數(shù)來包含要運(yùn)行的代碼
現(xiàn)在,您可以編寫作為腳本由從命令行執(zhí)行并導(dǎo)入且沒有副作用的Python代碼。接下來,您將學(xué)習(xí)如何編寫代碼并使其他程序員能輕松地理解其含義。
許多語言,如C,C++,Java以及其他的一些語言,都會(huì)定義一個(gè)叫做main()的函數(shù),當(dāng)編譯程序時(shí),操作系統(tǒng)會(huì)自動(dòng)調(diào)用該函數(shù)。此函數(shù)通常被稱為入口點(diǎn)(entry point),因?yàn)樗浅绦蜻M(jìn)入執(zhí)行的起始位置。
相比之下,Python沒有一個(gè)特殊的函數(shù)作為腳本的入口點(diǎn)。實(shí)際上在Python中可以將入口點(diǎn)定義成任何名稱。
盡管Python不要求將函數(shù)命名為main(),但是最佳的做法是將入口點(diǎn)函數(shù)命名為main()。這樣方便其他程序員定位程序的起點(diǎn)。
此外,main()函數(shù)應(yīng)該包含Python解釋器執(zhí)行文件時(shí)要運(yùn)行的任何代碼。這比將代碼放入條件語塊中更好,因?yàn)橛脩艨梢栽趯?dǎo)入模塊時(shí)重復(fù)使用main()函數(shù)。
修改best_practices.py文件如下所示:
在這個(gè)示例中,定義了一個(gè)main()函數(shù),它包含了上面的條件語句塊。之后修改條件語塊執(zhí)行main()。如果您將此代碼作為腳本運(yùn)行或?qū)耄瑢@得與上一節(jié)相同的輸出。
在main()中調(diào)用其他函數(shù)
另一種常見的實(shí)現(xiàn)方式是在main()中調(diào)用其他函數(shù),而不是直接將代碼寫入main()。這樣做的好處在于可以實(shí)現(xiàn)將幾個(gè)獨(dú)立運(yùn)行的子任務(wù)整合。
例如,某個(gè)腳本有如下功能:
如果在單獨(dú)的函數(shù)中各自實(shí)現(xiàn)這些子任務(wù),您(或其他用戶)可以很容易地實(shí)現(xiàn)代碼重用。之后您可以在main()函數(shù)中創(chuàng)建默認(rèn)的工作流。
您可以根據(jù)自己的情況選擇是否使用此方案。將任務(wù)拆分為多個(gè)函數(shù)會(huì)使重用更容易,但會(huì)增加他人理解代碼的難度。
修改best_practices.py文件如下所示:
在此示例代碼中,文件的前10行具有與之前相同的內(nèi)容。第12行的第二個(gè)函數(shù)創(chuàng)建并返回一些示例數(shù)據(jù),第17行的第三個(gè)函數(shù)模擬將修改后的數(shù)據(jù)寫入數(shù)據(jù)庫。
第21行定義了main()函數(shù)。在此示例中,對(duì)main()做出修改,它將調(diào)用數(shù)據(jù)讀取,數(shù)據(jù)處理以及數(shù)據(jù)寫入等功能。
首先,從read_data_from_web()中創(chuàng)建data。將data作為參數(shù)傳入process_data(),之后將返回modified_data。最后,將modified_data傳入write_data_to_database()。
腳本的最后兩行是條件語塊用于驗(yàn)證__name__,并且如果if語句為True,則執(zhí)行main()。
在命令行中運(yùn)行如下所示:
根據(jù)執(zhí)行結(jié)果,Python解釋器在執(zhí)行main()函數(shù)時(shí),將依次執(zhí)行read_data_from_web(),process_data()以及write_data_to_database()。當(dāng)然,您也可以導(dǎo)入best_practices.py文件并重用process_data()作為不同的數(shù)據(jù)輸入源,如下所示:
在此示例中,導(dǎo)入了best_practices并且將其簡(jiǎn)寫為bp。
導(dǎo)入過程會(huì)導(dǎo)致Python解釋器執(zhí)行best_practices.py的全部代碼,因此輸出顯示解釋文件用途的信息。
然后,從文件中存儲(chǔ)數(shù)據(jù)而不是從Web中讀取數(shù)據(jù)。之后,可以重用best_practices.py文件中的process_data()和write_data_to_database()函數(shù)。在此情況下,可以利用代碼重寫來取代在main()函數(shù)中實(shí)現(xiàn)全部的代碼邏輯。
實(shí)踐總結(jié)
以下是Python中main()函數(shù)的四個(gè)關(guān)鍵最佳實(shí)踐:
結(jié)論
恭喜!您現(xiàn)在已經(jīng)了解如何創(chuàng)建Python main()函數(shù)了。
本文介紹了如下內(nèi)容:
現(xiàn)在,您可以開始編寫一些非常棒的關(guān)于Python main()函數(shù)代碼啦!
網(wǎng)站名稱:關(guān)于python調(diào)試主函數(shù)的信息
網(wǎng)頁網(wǎng)址:http://chinadenli.net/article37/dsihspj.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站策劃、網(wǎng)站建設(shè)、虛擬主機(jī)、網(wǎng)站設(shè)計(jì)、用戶體驗(yàn)、網(wǎng)站排名
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)