python函數(shù)性能 python3 性能

在Python 中,yield 對遞歸函數(shù)的性能提高表現(xiàn)在哪里,程度如何?

遞歸式方法可以被用于解決很多的計算機(jī)科學(xué)問題,因此它是計算機(jī)科學(xué)中十分重要的一個概念。

創(chuàng)新互聯(lián)公司2013年成立,先為崇左等服務(wù)建站,崇左等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為崇左企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

絕大多數(shù)編程語言支持函數(shù)的自調(diào)用,在這些語言中函數(shù)可以通過調(diào)用自身來進(jìn)行遞歸。計算理論可以證明遞歸的作用可以完全取代循環(huán),因此在很多函數(shù)編程語言(如Scheme)中習(xí)慣用遞歸來實(shí)現(xiàn)循環(huán)。

計算機(jī)科學(xué)家尼克勞斯·維爾特如此描述遞歸:

遞歸的強(qiáng)大之處在于它允許用戶用有限的語句描述無限的對象。因此,在計算機(jī)科學(xué)中,遞歸可以被用來描述無限步的運(yùn)算,盡管描述運(yùn)算的程序是有限的。

python 2 遞歸函數(shù)和其它語言,基本沒有差別,只是不支持尾遞歸。無限遞歸最大值為固定的,但可以修改。

Python通過裝飾器并使用cprofile對函數(shù)進(jìn)行性能分析

Python中提供了很多接口方便我們能夠靈活進(jìn)行性能分析,包括cProfile模塊中的Profile類和pstat模塊中的Stats類。

--cprofile是一種確定性分析器,只測量CPU時間,并不關(guān)心內(nèi)存的消耗情況和其他與內(nèi)存相關(guān)聯(lián)的信息

--它是基于Isprof的用C語言實(shí)現(xiàn)的擴(kuò)展應(yīng)用,運(yùn)行開銷比較合理,適合分析運(yùn)行時間較長的程序

--enable(): 開始進(jìn)行性能分析并收集數(shù)據(jù)

--disableI(): 停止性能分析

--create_stats(): 停止收集數(shù)據(jù),并為已經(jīng)收集的數(shù)據(jù)創(chuàng)建stats對象

--print_stats():創(chuàng)建stats對象并打印分析結(jié)果

--dump_stats(filename): 把當(dāng)前性能分析的內(nèi)容寫入文件filename中

--runcall(func, *args, **kwargs): 收集被調(diào)用函數(shù)func的性能分析信息

--用來分析cProfile輸出的文件內(nèi)容

--pstas模塊為開發(fā)者提供了Stats類,可以讀取和操作stats文件

(Stats類可以接受stats文件名,也可以直接接受cProfile.Profile對象作為數(shù)據(jù)源。)

--strip_dirs(): 刪除報告中所有函數(shù)文件名的路徑信息

--dump_stats(filename): 把stats中的分析數(shù)據(jù)寫入文件(也可以寫成cProfile.Profile.dump_stats())

--sort_stats(*keys): 對報告列表進(jìn)行排序,函數(shù)會一次按照傳入的參數(shù)排序

--reverse_order(): 逆反當(dāng)前的排序

--print_stats(*restrictions): 把信息打印到標(biāo)準(zhǔn)輸出。*restrictions用于控制打印結(jié)果的形式,比如(10,1.0,".*.py.*")表示打印所有py文件的信息的前10行結(jié)果

--第一行表示運(yùn)行這個函數(shù)一共使用0.043秒,執(zhí)行了845次函數(shù)調(diào)用

--第二行表示結(jié)果是按什么順序排列的(這里表示按照調(diào)用次數(shù)來進(jìn)行排列的)

--ncalls: 表示函數(shù)調(diào)用的次數(shù)(有兩個數(shù)值表示有遞歸調(diào)用,總調(diào)用次數(shù)/原生調(diào)用次數(shù))

--tottime: 函數(shù)內(nèi)部調(diào)用時間(不包括他自己調(diào)用的其他函數(shù)時間)

--percall: tottime/ncalls

--cumtime: 表示累計調(diào)用時間(函數(shù)執(zhí)行玩的總時間),它包含了函數(shù)自己內(nèi)部調(diào)用的函數(shù)時間

--filename:lineno(function): 函數(shù)所在的文件,行號,函數(shù)名稱

上面的函數(shù)do_cProfile(do=False, order='tottime')是一個帶參數(shù)的裝飾器,通過do的值來進(jìn)行性能分析的開關(guān)控制,通過order的值來選擇輸出結(jié)果按照什么方式進(jìn)行排序。

比如我們對函數(shù)A和函數(shù)B進(jìn)行性能分析

如果不給裝飾器傳入?yún)?shù)的話就是默認(rèn)的False和tottime

「干貨」讓Python性能起飛的15個技巧,你知道幾個呢?

前言

Python 一直以來被大家所詬病的一點(diǎn)就是執(zhí)行速度慢,但不可否認(rèn)的是 Python 依然是我們學(xué)習(xí)和工作中的一大利器。本文總結(jié)了15個tips有助于提升 Python 執(zhí)行速度、優(yōu)化性能。

關(guān)于 Python 如何精確地測量程序的執(zhí)行時間,這個問題看起來簡單其實(shí)很復(fù)雜,因為程序的執(zhí)行時間受到很多因素的影響,例如操作系統(tǒng)、Python 版本以及相關(guān)硬件(CPU 性能、內(nèi)存讀寫速度)等。在同一臺電腦上運(yùn)行相同版本的語言時,上述因素就是確定的了,但是程序的睡眠時間依然是變化的,且電腦上正在運(yùn)行的其他程序也會對實(shí)驗有干擾,因此嚴(yán)格來說這就是實(shí)驗不可重復(fù)。

我了解到的關(guān)于計時比較有代表性的兩個庫就是 time 和 timeit 。

其中, time 庫中有 time() 、 perf_counter() 以及 process_time() 三個函數(shù)可用來計時(以秒為單位),加后綴 _ns 表示以納秒計時(自 Python3.7 始)。在此之前還有 clock() 函數(shù),但是在 Python3.3 之后被移除了。上述三者的區(qū)別如下:

與 time 庫相比, timeit 有兩個優(yōu)點(diǎn):

timeit.timeit(stmt='pass', setup='pass', timer= , number=1000000, globals=None) 參數(shù)說明:

本文所有的計時均采用 timeit 方法,且采用默認(rèn)的執(zhí)行次數(shù)一百萬次。

為什么要執(zhí)行一百萬次呢?因為我們的測試程序很短,如果不執(zhí)行這么多次的話,根本看不出差距。

Exp1:將字符串?dāng)?shù)組中的小寫字母轉(zhuǎn)為大寫字母。

測試數(shù)組為 oldlist = ['life', 'is', 'short', 'i', 'choose', 'python']。

方法一

方法二

方法一耗時 0.5267724000000005s ,方法二耗時 0.41462569999999843s ,性能提升 21.29%

Exp2:求兩個 list 的交集。

測試數(shù)組:a = [1,2,3,4,5],b = [2,4,6,8,10]。

方法一

方法二

方法一耗時 0.9507264000000006s ,方法二耗時 0.6148200999999993s ,性能提升 35.33%

關(guān)于 set() 的語法: | 、 、 - 分別表示求并集、交集、差集。

我們可以通過多種方式對序列進(jìn)行排序,但其實(shí)自己編寫排序算法的方法有些得不償失。因為內(nèi)置的 sort() 或 sorted() 方法已經(jīng)足夠優(yōu)秀了,且利用參數(shù) key 可以實(shí)現(xiàn)不同的功能,非常靈活。二者的區(qū)別是 sort() 方法僅被定義在 list 中,而 sorted() 是全局方法對所有的可迭代序列都有效。

Exp3:分別使用快排和 sort() 方法對同一列表排序。

測試數(shù)組:lists = [2,1,4,3,0]。

方法一

方法二

方法一耗時 2.4796975000000003s ,方法二耗時 0.05551999999999424s ,性能提升 97.76%

順帶一提, sorted() 方法耗時 0.1339823999987857s 。

可以看出, sort() 作為 list 專屬的排序方法還是很強(qiáng)的, sorted() 雖然比前者慢一點(diǎn),但是勝在它“不挑食”,它對所有的可迭代序列都有效。

擴(kuò)展 :如何定義 sort() 或 sorted() 方法的 key

1.通過 lambda 定義

2.通過 operator 定義

operator 的 itemgetter() 適用于普通數(shù)組排序, attrgetter() 適用于對象數(shù)組排序

3.通過 cmp_to_key() 定義,最為靈活

Exp4:統(tǒng)計字符串中每個字符出現(xiàn)的次數(shù)。

測試數(shù)組:sentence='life is short, i choose python'。

方法一

方法二

方法一耗時 2.8105250000000055s ,方法二耗時 1.6317423000000062s ,性能提升 41.94%

列表推導(dǎo)(list comprehension)短小精悍。在小代碼片段中,可能沒有太大的區(qū)別。但是在大型開發(fā)中,它可以節(jié)省一些時間。

Exp5:對列表中的奇數(shù)求平方,偶數(shù)不變。

測試數(shù)組:oldlist = range(10)。

方法一

方法二

方法一耗時 1.5342976000000021s ,方法二耗時 1.4181957999999923s ,性能提升 7.57%

大多數(shù)人都習(xí)慣使用 + 來連接字符串。但其實(shí),這種方法非常低效。因為, + 操作在每一步中都會創(chuàng)建一個新字符串并復(fù)制舊字符串。更好的方法是用 join() 來連接字符串。關(guān)于字符串的其他操作,也盡量使用內(nèi)置函數(shù),如 isalpha() 、 isdigit() 、 startswith() 、 endswith() 等。

Exp6:將字符串列表中的元素連接起來。

測試數(shù)組:oldlist = ['life', 'is', 'short', 'i', 'choose', 'python']。

方法一

方法二

方法一耗時 0.27489080000000854s ,方法二耗時 0.08166570000000206s ,性能提升 70.29%

join 還有一個非常舒服的點(diǎn),就是它可以指定連接的分隔符,舉個例子

life//is//short//i//choose//python

Exp6:交換x,y的值。

測試數(shù)據(jù):x, y = 100, 200。

方法一

方法二

方法一耗時 0.027853900000010867s ,方法二耗時 0.02398730000000171s ,性能提升 13.88%

在不知道確切的循環(huán)次數(shù)時,常規(guī)方法是使用 while True 進(jìn)行無限循環(huán),在代碼塊中判斷是否滿足循環(huán)終止條件。雖然這樣做沒有任何問題,但 while 1 的執(zhí)行速度比 while True 更快。因為它是一種數(shù)值轉(zhuǎn)換,可以更快地生成輸出。

Exp8:分別用 while 1 和 while True 循環(huán) 100 次。

方法一

方法二

方法一耗時 3.679268300000004s ,方法二耗時 3.607847499999991s ,性能提升 1.94%

將文件存儲在高速緩存中有助于快速恢復(fù)功能。Python 支持裝飾器緩存,該緩存在內(nèi)存中維護(hù)特定類型的緩存,以實(shí)現(xiàn)最佳軟件驅(qū)動速度。我們使用 lru_cache 裝飾器來為斐波那契函數(shù)提供緩存功能,在使用 fibonacci 遞歸函數(shù)時,存在大量的重復(fù)計算,例如 fibonacci(1) 、 fibonacci(2) 就運(yùn)行了很多次。而在使用了 lru_cache 后,所有的重復(fù)計算只會執(zhí)行一次,從而大大提高程序的執(zhí)行效率。

Exp9:求斐波那契數(shù)列。

測試數(shù)據(jù):fibonacci(7)。

方法一

方法二

方法一耗時 3.955014900000009s ,方法二耗時 0.05077979999998661s ,性能提升 98.72%

注意事項:

我被執(zhí)行了(執(zhí)行了兩次 demo(1, 2) ,卻只輸出一次)

functools.lru_cache(maxsize=128, typed=False) 的兩個可選參數(shù):

點(diǎn)運(yùn)算符( . )用來訪問對象的屬性或方法,這會引起程序使用 __getattribute__() 和 __getattr__() 進(jìn)行字典查找,從而帶來不必要的開銷。尤其注意,在循環(huán)當(dāng)中,更要減少點(diǎn)運(yùn)算符的使用,應(yīng)該將它移到循環(huán)外處理。

這啟發(fā)我們應(yīng)該盡量使用 from ... import ... 這種方式來導(dǎo)包,而不是在需要使用某方法時通過點(diǎn)運(yùn)算符來獲取。其實(shí)不光是點(diǎn)運(yùn)算符,其他很多不必要的運(yùn)算我們都盡量移到循環(huán)外處理。

Exp10:將字符串?dāng)?shù)組中的小寫字母轉(zhuǎn)為大寫字母。

測試數(shù)組為 oldlist = ['life', 'is', 'short', 'i', 'choose', 'python']。

方法一

方法二

方法一耗時 0.7235491999999795s ,方法二耗時 0.5475435999999831s ,性能提升 24.33%

當(dāng)我們知道具體要循環(huán)多少次時,使用 for 循環(huán)比使用 while 循環(huán)更好。

Exp12:使用 for 和 while 分別循環(huán) 100 次。

方法一

方法二

方法一耗時 3.894683299999997s ,方法二耗時 1.0198077999999953s ,性能提升 73.82%

Numba 可以將 Python 函數(shù)編譯碼為機(jī)器碼執(zhí)行,大大提高代碼執(zhí)行速度,甚至可以接近 C 或 FORTRAN 的速度。它能和 Numpy 配合使用,在 for 循環(huán)中或存在大量計算時能顯著地提高執(zhí)行效率。

Exp12:求從 1 加到 100 的和。

方法一

方法二

方法一耗時 3.7199997000000167s ,方法二耗時 0.23769430000001535s ,性能提升 93.61%

矢量化是 NumPy 中的一種強(qiáng)大功能,可以將操作表達(dá)為在整個數(shù)組上而不是在各個元素上發(fā)生。這種用數(shù)組表達(dá)式替換顯式循環(huán)的做法通常稱為矢量化。

在 Python 中循環(huán)數(shù)組或任何數(shù)據(jù)結(jié)構(gòu)時,會涉及很多開銷。NumPy 中的向量化操作將內(nèi)部循環(huán)委托給高度優(yōu)化的 C 和 Fortran 函數(shù),從而使 Python 代碼更加快速。

Exp13:兩個長度相同的序列逐元素相乘。

測試數(shù)組:a = [1,2,3,4,5], b = [2,4,6,8,10]

方法一

方法二

方法一耗時 0.6706845000000214s ,方法二耗時 0.3070132000000001s ,性能提升 54.22%

若要檢查列表中是否包含某成員,通常使用 in 關(guān)鍵字更快。

Exp14:檢查列表中是否包含某成員。

測試數(shù)組:lists = ['life', 'is', 'short', 'i', 'choose', 'python']

方法一

方法二

方法一耗時 0.16038449999999216s ,方法二耗時 0.04139250000000061s ,性能提升 74.19%

itertools 是用來操作迭代器的一個模塊,其函數(shù)主要可以分為三類:無限迭代器、有限迭代器、組合迭代器。

Exp15:返回列表的全排列。

測試數(shù)組:["Alice", "Bob", "Carol"]

方法一

方法二

方法一耗時 3.867292899999484s ,方法二耗時 0.3875405000007959s ,性能提升 89.98%

根據(jù)上面的測試數(shù)據(jù),我繪制了下面這張實(shí)驗結(jié)果圖,可以更加直觀的看出不同方法帶來的性能差異。

從圖中可以看出,大部分的技巧所帶來的性能增幅還是比較可觀的,但也有少部分技巧的增幅較?。ɡ缇幪?、7、8,其中,第 8 條的兩種方法幾乎沒有差異)。

總結(jié)下來,我覺得其實(shí)就是下面這兩條原則:

內(nèi)置庫函數(shù)由專業(yè)的開發(fā)人員編寫并經(jīng)過了多次測試,很多庫函數(shù)的底層是用 C 語言開發(fā)的。因此,這些函數(shù)總體來說是非常高效的(比如 sort() 、 join() 等),自己編寫的方法很難超越它們,還不如省省功夫,不要重復(fù)造輪子了,何況你造的輪子可能更差。所以,如果函數(shù)庫中已經(jīng)存在該函數(shù),就直接拿來用。

有很多優(yōu)秀的第三方庫,它們的底層可能是用 C 和 Fortran 來實(shí)現(xiàn)的,像這樣的庫用起來絕對不會吃虧,比如前文提到的 Numpy 和 Numba,它們帶來的提升都是非常驚人的。類似這樣的庫還有很多,比如Cython、PyPy等,這里我只是拋磚引玉。

原文鏈接:

python函數(shù)傳對象對性能的影響

python函數(shù)傳對象對性能有影響。在Python中,一切皆對象,Python參數(shù)傳遞采用的都是“傳對象引用”的方式。實(shí)際上,這種方式相當(dāng)于傳值和傳引用的一種綜合。如果函數(shù)收到的是一個可變對象(比如字典或者列表)的引用,就能修改對象的原始值,相當(dāng)于通過“傳引用”來傳遞對象。如果函數(shù)收到的是一個不可變對象(比如數(shù)字、字符或者元組)的引用,就不能直接修改原始對象,相當(dāng)于通過“傳值’來傳遞對象,此時如果想改變這些變量的值,可以將這些變量申明為全局變量。

后端編程Python3-調(diào)試、測試和性能剖析(下)

單元測試(Unit Testing)

為程序編寫測試——如果做的到位——有助于減少bug的出現(xiàn),并可以提高我們對程序按預(yù)期目標(biāo)運(yùn)行的信心。通常,測試并不能保證正確性,因為對大多數(shù)程序而言, 可能的輸入范圍以及可能的計算范圍是如此之大,只有其中最小的一部分能被實(shí)際地進(jìn) 行測試。盡管如此,通過仔細(xì)地選擇測試的方法和目標(biāo),可以提高代碼的質(zhì)量。

大量不同類型的測試都可以進(jìn)行,比如可用性測試、功能測試以及整合測試等。這里, 我們只講單元測試一對單獨(dú)的函數(shù)、類與方法進(jìn)行測試,確保其符合預(yù)期的行為。

TDD的一個關(guān)鍵點(diǎn)是,當(dāng)我們想添加一個功能時——比如為類添加一個方法—— 我們首次為其編寫一個測試用例。當(dāng)然,測試將失敗,因為我們還沒有實(shí)際編寫該方法。現(xiàn)在,我們編寫該方法,一旦方法通過了測試,就可以返回所有測試,確保我們新添加的代碼沒有任何預(yù)期外的副作用。一旦所有測試運(yùn)行完畢(包括我們?yōu)樾鹿δ芫帉懙臏y試),就可以對我們的代碼進(jìn)行檢查,并有理有據(jù)地相信程序行為符合我們的期望——當(dāng)然,前提是我們的測試是適當(dāng)?shù)摹?/p>

比如,我們編寫了一個函數(shù),該函數(shù)在特定的索引位置插入一個字符串,可以像下面這樣開始我們的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

對不返回任何參數(shù)的函數(shù)或方法(通常返回None),我們通常賦予其由pass構(gòu)成的一個suite,對那些返回值被試用的,我們或者返回一個常數(shù)(比如0),或者某個不變的參數(shù)——這也是我們這里所做的。(在更復(fù)雜的情況下,返回fake對象可能更有用一一對這樣的類,提供mock對象的第三方模塊是可用的。)

運(yùn)行doctest時會失敗,并列出每個預(yù)期內(nèi)的字符串('ABCD-EF'、'ABCDE-F' 等),及其實(shí)際獲取的字符串(所有的都是'ABCD-EF')。一旦確定doctest是充分的和正確的,就可以編寫該函數(shù)的主體部分,在本例中只是簡單的return string[:position] + insert+string[position:]。(如果我們編寫的是 return string[:position] + insert,之后復(fù)制 string [:position]并將其粘貼在末尾以便減少一些輸入操作,那么doctest會立即提示錯誤。)

Python的標(biāo)準(zhǔn)庫提供了兩個單元測試模塊,一個是doctest,這里和前面都簡單地提到過,另一個是unittest。此外,還有一些可用于Python的第三方測試工具。其中最著名的兩個是nose (code.google.com/p/python-nose)與py.test (codespeak.net/py/dist/test/test.html), nose 致力于提供比標(biāo)準(zhǔn)的unittest 模塊更廣泛的功能,同時保持與該模塊的兼容性,py.test則采用了與unittest有些不同的方法,試圖盡可能消除樣板測試代碼。這兩個第三方模塊都支持測試發(fā)現(xiàn),因此沒必要寫一個總體的測試程序——因為模塊將自己搜索測試程序。這使得測試整個代碼樹或某一部分 (比如那些已經(jīng)起作用的模塊)變得很容易。那些對測試嚴(yán)重關(guān)切的人,在決定使用哪個測試工具之前,對這兩個(以及任何其他有吸引力的)第三方模塊進(jìn)行研究都是值 得的。

創(chuàng)建doctest是直截了當(dāng)?shù)模何覀冊谀K中編寫測試、函數(shù)、類與方法的docstrings。 對于模塊,我們簡單地在末尾添加了 3行:

if __name__ =="__main__":

import doctest

doctest.testmod()

在程序內(nèi)部使用doctest也是可能的。比如,blocks.py程序(其模塊在后面)有自己函數(shù)的doctest,但以如下代碼結(jié)尾:

if __name__== "__main__":

main()

這里簡單地調(diào)用了程序的main()函數(shù),并且沒有執(zhí)行程序的doctest。要實(shí)驗程序的 doctest,有兩種方法。一種是導(dǎo)入doctest模塊,之后運(yùn)行程序---比如,在控制臺中輸 入 python3 -m doctest blocks.py (在 Wndows 平臺上,使用類似于 C:Python3 lpython.exe 這樣的形式替代python3)。如果所有測試運(yùn)行良好,就沒有輸出,因此,我們可能寧愿執(zhí)行python3-m doctest blocks.py-v,因為這會列出每個執(zhí)行的doctest,并在最后給出結(jié)果摘要。

另一種執(zhí)行doctest的方法是使用unittest模塊創(chuàng)建單獨(dú)的測試程序。在概念上, unittest模塊是根據(jù)Java的JUnit單元測試庫進(jìn)行建模的,并用于創(chuàng)建包含測試用例的測試套件。unittest模塊可以基于doctests創(chuàng)建測試用例,而不需要知道程序或模塊包含的任何事物——只要知道其包含doctest即可。因此,為給blocks.py程序制作一個測試套件,我們可以創(chuàng)建如下的簡單程序(將其稱為test_blocks.py):

import doctest

import unittest

import blocks

suite = unittest.TestSuite()

suite.addTest(doctest.DocTestSuite(blocks))

runner = unittest.TextTestRunner()

print(runner.run(suite))

注意,如果釆用這種方法,程序的名稱上會有一個隱含的約束:程序名必須是有效的模塊名。因此,名為convert-incidents.py的程序的測試不能寫成這樣。因為import convert-incidents不是有效的,在Python標(biāo)識符中,連接符是無效的(避開這一約束是可能的,但最簡單的解決方案是使用總是有效模塊名的程序文件名,比如,使用下劃線替換連接符)。這里展示的結(jié)構(gòu)(創(chuàng)建一個測試套件,添加一個或多個測試用例或測試套件,運(yùn)行總體的測試套件,輸出結(jié)果)是典型的機(jī)遇unittest的測試。運(yùn)行時,這一特定實(shí)例產(chǎn)生如下結(jié)果:

...

.............................................................................................................

Ran 3 tests in 0.244s

OK

每次執(zhí)行一個測試用例時,都會輸出一個句點(diǎn)(因此上面的輸出最前面有3個句點(diǎn)),之后是一行連接符,再之后是測試摘要(如果有任何一個測試失敗,就會有更多的輸出信息)。

如果我們嘗試將測試分離開(典型情況下是要測試的每個程序和模塊都有一個測試用例),就不要再使用doctests,而是直接使用unittest模塊的功能——尤其是我們習(xí)慣于使用JUnit方法進(jìn)行測試時ounittest模塊會將測試分離于代碼——對大型項目(測試編寫人員與開發(fā)人員可能不一致)而言,這種方法特別有用。此外,unittest單元測試編寫為獨(dú)立的Python模塊,因此,不會像在docstring內(nèi)部編寫測試用例時受到兼容性和明智性的限制。

unittest模塊定義了 4個關(guān)鍵概念。測試夾具是一個用于描述創(chuàng)建測試(以及用完之后將其清理)所必需的代碼的術(shù)語,典型實(shí)例是創(chuàng)建測試所用的一個輸入文件,最后刪除輸入文件與結(jié)果輸出文件。測試套件是一組測試用例的組合。測試用例是測試的基本單元—我們很快就會看到實(shí)例。測試運(yùn)行者是執(zhí)行一個或多個測試套件的對象。

典型情況下,測試套件是通過創(chuàng)建unittest.TestCase的子類實(shí)現(xiàn)的,其中每個名稱 以“test”開頭的方法都是一個測試用例。如果我們需要完成任何創(chuàng)建操作,就可以在一個名為setUp()的方法中實(shí)現(xiàn);類似地,對任何清理操作,也可以實(shí)現(xiàn)一個名為 tearDown()的方法。在測試內(nèi)部,有大量可供我們使用的unittest.TestCase方法,包括 assertTrue()、assertEqual()、assertAlmostEqual()(對于測試浮點(diǎn)數(shù)很有用)、assertRaises() 以及更多,還包括很多對應(yīng)的逆方法,比如assertFalse()、assertNotEqual()、failIfEqual()、 failUnlessEqual ()等。

unittest模塊進(jìn)行了很好的歸檔,并且提供了大量功能,但在這里我們只是通過一 個非常簡單的測試套件來感受一下該模塊的使用。這里將要使用的實(shí)例,該練習(xí)要求創(chuàng)建一個Atomic模塊,該模塊可以用作一 個上下文管理器,以確保或者所有改變都應(yīng)用于某個列表、集合或字典,或者所有改變都不應(yīng)用。作為解決方案提供的Atomic.py模塊使用30行代碼來實(shí)現(xiàn)Atomic類, 并提供了 100行左右的模塊doctest。這里,我們將創(chuàng)建test_Atomic.py模塊,并使用 unittest測試替換doctest,以便可以刪除doctest。

在編寫測試模塊之前,我們需要思考都需要哪些測試。我們需要測試3種不同的數(shù)據(jù)類型:列表、集合與字典。對于列表,需要測試的是插入項、刪除項或修改項的值。對于集合,我們必須測試向其中添加或刪除一個項。對于字典,我們必須測試的是插入一個項、修改一個項的值、刪除一個項。此外,還必須要測試的是在失敗的情況下,不會有任何改變實(shí)際生效。

結(jié)構(gòu)上看,測試不同數(shù)據(jù)類型實(shí)質(zhì)上是一樣的,因此,我們將只為測試列表編寫測試用例,而將其他的留作練習(xí)。test_Atomic.py模塊必須導(dǎo)入unittest模塊與要進(jìn)行測試的Atomic模塊。

創(chuàng)建unittest文件時,我們通常創(chuàng)建的是模塊而非程序。在每個模塊內(nèi)部,我們定義一個或多個unittest.TestCase子類。比如,test_Atomic.py模塊中僅一個單獨(dú)的 unittest-TestCase子類,也就是TestAtomic (稍后將對其進(jìn)行講解),并以如下兩行結(jié)束:

if name == "__main__":

unittest.main()

這兩行使得該模塊可以單獨(dú)運(yùn)行。當(dāng)然,該模塊也可以被導(dǎo)入并從其他測試程序中運(yùn)行——如果這只是多個測試套件中的一個,這一點(diǎn)是有意義的。

如果想要從其他測試程序中運(yùn)行test_Atomic.py模塊,那么可以編寫一個與此類似的程序。我們習(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)建了一個單獨(dú)的套件,這是通過讓unittest模塊讀取test_Atomic 模塊實(shí)現(xiàn)的,并且使用其每一個test*()方法(本實(shí)例中是test_list_success()、test_list_fail(),稍后很快就會看到)作為測試用例。

我們現(xiàn)在將查看TestAtomic類的實(shí)現(xiàn)。對通常的子類(不包括unittest.TestCase 子類),不怎么常見的是,沒有必要實(shí)現(xiàn)初始化程序。在這一案例中,我們將需要建立 一個方法,但不需要清理方法,并且我們將實(shí)現(xiàn)兩個測試用例。

def setUp(self):

self.original_list = list(range(10))

我們已經(jīng)使用了 unittest.TestCase.setUp()方法來創(chuàng)建單獨(dú)的測試數(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)

這里,我們直接在測試方法中編寫了測試代碼,而不需要一個內(nèi)部函數(shù),也不再使用unittest.TestCase.assertRaised()作為上下文管理器(期望代碼產(chǎn)生AttributeError)。 最后我們也使用了 Python 3.1 的 unittest.TestCase.assertListEqual()方法。

正如我們已經(jīng)看到的,Python的測試模塊易于使用,并且極為有用,在我們使用 TDD的情況下更是如此。它們還有比這里展示的要多得多的大量功能與特征——比如,跳過測試的能力,這有助于理解平臺差別——并且這些都有很好的文檔支持。缺失的一個功能——但nose與py.test提供了——是測試發(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ā)生的地方,而不只是檢査代碼并試圖對其進(jìn)行優(yōu)化。 隨機(jī)優(yōu)化會導(dǎo)致引入bug,或者對程序中本來對程序整體性能并沒有實(shí)際影響的部分進(jìn)行提速,而這并非解釋器耗費(fèi)大部分時間的地方。

在深入討論profiling之前,注意一些易于學(xué)習(xí)和使用的Python程序設(shè)計習(xí)慣是有意義的,并且對提高程序性能不無裨益。這些技術(shù)都不是特定于某個Python版本的, 而是合理的Python程序設(shè)計風(fēng)格。第一,在需要只讀序列時,最好使用元組而非列表; 第二,使用生成器,而不是創(chuàng)建大的元組和列表并在其上進(jìn)行迭代處理;第三,盡量使用Python內(nèi)置的數(shù)據(jù)結(jié)構(gòu) dicts、lists、tuples 而不實(shí)現(xiàn)自己的自定義結(jié)構(gòu),因為內(nèi)置的數(shù)據(jù)結(jié)構(gòu)都是經(jīng)過了高度優(yōu)化的;第四,從小字符串中產(chǎn)生大字符串時, 不要對小字符串進(jìn)行連接,而是在列表中累積,最后將字符串列表結(jié)合成為一個單獨(dú)的字符串;第五,也是最后一點(diǎn),如果某個對象(包括函數(shù)或方法)需要多次使用屬性進(jìn)行訪問(比如訪問模塊中的某個函數(shù)),或從某個數(shù)據(jù)結(jié)構(gòu)中進(jìn)行訪問,那么較好的做法是創(chuàng)建并使用一個局部變量來訪問該對象,以便提供更快的訪問速度。

Python標(biāo)準(zhǔn)庫提供了兩個特別有用的模塊,可以輔助調(diào)査代碼的性能問題。一個是timeit模塊——該模塊可用于對一小段Python代碼進(jìn)行計時,并可用于諸如對兩個或多個特定函數(shù)或方法的性能進(jìn)行比較等場合。另一個是cProfile模塊,可用于profile 程序的性能——該模塊對調(diào)用計數(shù)與次數(shù)進(jìn)行了詳細(xì)分解,以便發(fā)現(xiàn)性能瓶頸所在。

為了解timeit模塊,我們將查看一些小實(shí)例。假定有3個函數(shù)function_a()、 function_b()、function_c(), 3個函數(shù)執(zhí)行同樣的計算,但分別使用不同的算法。如果將這些函數(shù)放于同一個模塊中(或分別導(dǎo)入),就可以使用timeit模塊對其進(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)造子的第一個參數(shù)是我們想要執(zhí)行并計時的代碼,其形式是字符串。這里,該字符串是“function_a(X,Y)”;第二個參數(shù)是可選的,還是一個待執(zhí)行的字符串,這一次是在待計時的代碼之前,以便提供一些建立工作。這里,我們從 __main__ (即this)模塊導(dǎo)入了待測試的函數(shù),還有兩個作為輸入數(shù)據(jù)傳入的變量(X 與Y),這兩個變量在該模塊中是作為全局變量提供的。我們也可以很輕易地像從其他模塊中導(dǎo)入數(shù)據(jù)一樣來進(jìn)行導(dǎo)入操作。

調(diào)用timeit.Timer對象的timeit()方法時,首先將執(zhí)行構(gòu)造子的第二個參數(shù)(如果有), 之后執(zhí)行構(gòu)造子的第一個參數(shù)并對其執(zhí)行時間進(jìn)行計時。timeit.Timer.timeit()方法的返回值是以秒計數(shù)的時間,類型是float。默認(rèn)情況下,timeit()方法重復(fù)100萬次,并返回所 有這些執(zhí)行的總秒數(shù),但在這一特定案例中,只需要1000次反復(fù)就可以給出有用的結(jié)果, 因此對重復(fù)計數(shù)次數(shù)進(jìn)行了顯式指定。在對每個函數(shù)進(jìn)行計時后,使用重復(fù)次數(shù)對總數(shù)進(jìn)行除法操作,就得到了平均執(zhí)行時間,并在控制臺中打印出函數(shù)名與執(zhí)行時間。

function_a() 0.001618 sec

function_b() 0.012786 sec

function_c() 0.003248 sec

在這一實(shí)例中,function_a()顯然是最快的——至少對于這里使用的輸入數(shù)據(jù)而言。 在有些情況下一一比如輸入數(shù)據(jù)不同會對性能產(chǎn)生巨大影響——可能需要使用多組輸入數(shù)據(jù)對每個函數(shù)進(jìn)行測試,以便覆蓋有代表性的測試用例,并對總執(zhí)行時間或平均執(zhí)行時間進(jìn)行比較。

有時監(jiān)控自己的代碼進(jìn)行計時并不是很方便,因此timeit模塊提供了一種在命令行中對代碼執(zhí)行時間進(jìn)行計時的途徑。比如,要對MyModule.py模塊中的函數(shù)function_a()進(jìn)行計時,可以在控制臺中輸入如下命令:python3 -m timeit -n 1000 -s "from MyModule import function_a, X, Y" "function_a(X, Y)"(與通常所做的一樣,對 Windows 環(huán)境,我們必須使用類似于C:Python3lpython.exe這樣的內(nèi)容來替換python3)。-m選項用于Python 解釋器,使其可以加載指定的模塊(這里是timeit),其他選項則由timeit模塊進(jìn)行處理。 -n選項指定了循環(huán)計數(shù)次數(shù),-s選項指定了要建立,最后一個參數(shù)是要執(zhí)行和計時的代碼。命令完成后,會向控制臺中打印運(yùn)行結(jié)果,比如:

1000 loops, best of 3: 1.41 msec per loop

之后我們可以輕易地對其他兩個函數(shù)進(jìn)行計時,以便對其進(jìn)行整體的比較。

cProfile模塊(或者profile模塊,這里統(tǒng)稱為cProfile模塊)也可以用于比較函數(shù) 與方法的性能。與只是提供原始計時的timeit模塊不同的是,cProfile模塊精確地展示 了有什么被調(diào)用以及每個調(diào)用耗費(fèi)了多少時間。下面是用于比較與前面一樣的3個函數(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)建,因為模塊函數(shù)會使用內(nèi)省來尋找需要使用的函數(shù)與變量。這里沒有使用顯式的print()語句,因為默認(rèn)情況下,cProfile.run()函數(shù)會在控制臺中打印其輸出。下面給出的是所有函數(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ù)")列列出了對指定函數(shù)(在filename:lineno(function)中列出) 的調(diào)用次數(shù)?;叵胍幌挛覀冎貜?fù)了 1000次調(diào)用,因此必須將這個次數(shù)記住。tottime (“總的時間”)列列出了某個函數(shù)中耗費(fèi)的總時間,但是排除了函數(shù)調(diào)用的其他函數(shù)內(nèi)部花費(fèi)的時間。第一個percall列列出了對函數(shù)的每次調(diào)用的平均時間(tottime // ncalls)。 cumtime ("累積時間")列出了在函數(shù)中耗費(fèi)的時間,并且包含了函數(shù)調(diào)用的其他函數(shù)內(nèi)部花費(fèi)的時間。第二個percall列列出了對函數(shù)的每次調(diào)用的平均時間,包括其調(diào)用的函數(shù)耗費(fèi)的時間。

這種輸出信息要比timeit模塊的原始計時信息富有啟發(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() 模塊提供了足夠的信息來查看計時上存在的這些差別,但cProfile模塊允許我們了解為什么會存在這些差別。正如timeit模塊允許對代碼進(jìn)行計時而又不需要對其監(jiān)控一樣,cProfile模塊也可以做到這一點(diǎn)。然而,從命令行使用cProfile模塊時,我們不能精確地指定要執(zhí)行的 是什么——而只是執(zhí)行給定的程序或模塊,并報告所有這些的計時結(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模塊對于識別值得進(jìn)一步研究的區(qū)域是有用的。比如,這里 我們可以清晰地看到function_b()需要耗費(fèi)更長的時間,但是我們怎樣獲取進(jìn)一步的詳細(xì)資料?我們可以使用cProfile.run("function_b()")來替換對function_b()的調(diào)用?;蛘呖梢员4嫱耆膒rofile數(shù)據(jù)并使用pstats模塊對其進(jìn)行分析。要保存profile,就必須對命令行進(jìn)行稍許修改:python3 -m cProfile -o profileDataFile programOrModule.py。 之后可以對 profile 數(shù)據(jù)進(jìn)行分析,比如啟動IDLE,導(dǎo)入pstats模塊,賦予其已保存的profileDataFile,或者也可以在控制臺中交互式地使用pstats。

下面給出的是一個非常短的控制臺會話實(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模塊,我們可以識別出我們自己代碼中哪些區(qū)域會耗費(fèi)超過預(yù)期的時間;使用cProfile模塊,還可以準(zhǔn)確算岀時間消耗在哪里。

以上內(nèi)容部分摘自視頻課程 05后端編程Python-19調(diào)試、測試和性能調(diào)優(yōu)(下) ,更多實(shí)操示例請參照視頻講解。跟著張員外講編程,學(xué)習(xí)更輕松,不花錢還能學(xué)習(xí)真本領(lǐng)。

本文題目:python函數(shù)性能 python3 性能
網(wǎng)站地址:http://muchs.cn/article8/dossgop.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google電子商務(wù)、全網(wǎng)營銷推廣手機(jī)網(wǎng)站建設(shè)、網(wǎng)站建設(shè)、定制網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)頁設(shè)計公司