盤點(diǎn)提高 Python 代碼效率的方法
第一招:蛇打七寸:定位瓶頸
首先,第一步是定位瓶頸。舉個(gè)簡(jiǎn)單的栗子,一個(gè)函數(shù)可以從1秒優(yōu)化到到0.9秒,另一個(gè)函數(shù)可以從1分鐘優(yōu)化到30秒,如果要花的代價(jià)相同,而且時(shí)間限制只能搞定一個(gè),搞哪個(gè)?根據(jù)短板原理,當(dāng)然選第二個(gè)啦。
一個(gè)有經(jīng)驗(yàn)的程序員在這里一定會(huì)遲疑一下,等等?函數(shù)?這么說(shuō),還要考慮調(diào)用次數(shù)?如果第一個(gè)函數(shù)在整個(gè)程序中需要被調(diào)用100000次,第二個(gè)函數(shù)在整個(gè)程序中被調(diào)用1次,這個(gè)就不一定了。舉這個(gè)栗子,是想說(shuō)明,程序的瓶頸有的時(shí)候不一定一眼能看出來(lái)。還是上面那個(gè)選擇,程序員的你應(yīng)該有感覺(jué)的,大多數(shù)情況下:一個(gè)「可以」從一分鐘優(yōu)化到30秒的函數(shù)會(huì)比一個(gè)「可以」從1秒優(yōu)化到0.9秒的函數(shù)更容易捕獲我們的注意,因?yàn)橛泻艽蟮倪M(jìn)步空間嘛。
所以,這么多廢話講完,獻(xiàn)上第一招,profile。這是 python 自帶的定位程序瓶頸的利器!雖然它提供了三種選項(xiàng)profile,cProfile,hotshot。還分為內(nèi)置和外置。但是,個(gè)人覺(jué)得一種足矣,外置cProfile。心法如下:
python -m profile 逗比程序.py
這招的效果會(huì)輸出一系列東西,比如函數(shù)被調(diào)用了幾次,總時(shí)間多少,其中有多少是這個(gè)函數(shù)的子函數(shù)花費(fèi)的,每次花多少時(shí)間,等等。嘛一圖勝千言:
filename:lineno(function): 文件名:第幾行(函數(shù)名)
ncalls: 這貨一共調(diào)用了幾次
tottime: 這貨自己總共花了多少時(shí)間,也就是要除掉內(nèi)部函數(shù)小弟們的花費(fèi)
percall: 平均每次調(diào)用花的時(shí)間,tottime 除以 ncalls
cumtime: 這貨還有它的所有內(nèi)部函數(shù)小弟們的總花費(fèi)
percall: 跟上面那個(gè) percall 差不多,不過(guò)是 cumtime 除以 ncalls
找到最值得優(yōu)化的點(diǎn),然后干吧。
第二招:一蛇禪:只需一招
記得剛開(kāi)始接觸 Python 的時(shí)候,有一位學(xué)長(zhǎng)告訴我,Python 有一個(gè)牛逼的理想,它希望每一個(gè)用它的人能寫(xiě)出一模一樣的程序。Python 之禪有云:
There should be one-- and preferably only one --obvious way to do it
所以 Python 系專業(yè)的禪師提供了一些常用功能的 only one 的寫(xiě)法。本人看了一下傳說(shuō)中的PythonWiKi:PerformanceTips,總結(jié)了幾個(gè)「不要醬紫」「要醬紫」。
合并字符串的時(shí)候不要醬紫:
s = "" for substring in list: s += substring
要醬紫:
s = "".join(slist)
格式化字符串的時(shí)候不要醬紫:
out = "<html>" + head + prologue + query + tail + "</html>"
要醬紫:
out = "<html>%s%s%s%s</html>" % (head, prologue, query, tail)
可以不用循環(huán)的時(shí)候就不要用循環(huán),比如不要醬紫:
newlist = [] for word in oldlist: newlist.append(word.upper())
要醬紫:
newlist = map(str.upper, oldlist)
或者醬紫:
newlist = [s.upper() for s in oldlist]
字典初始化,比較常用的:
wdict = {} for word in words: if word not in wdict: wdict[word] = 0 wdict[word] += 1
如果重復(fù)的 word 太多了的話,可以考慮用醬紫的模式來(lái)省掉大量判斷:
wdict = {} for word in words: try: wdict[word] += 1 except KeyError: wdict[word] = 1
盡量減少 function 調(diào)用次數(shù),用內(nèi)部循環(huán)代替,比如,不要醬紫:
x = 0 def doit1(i): global x x = x + i list = range(100000) t = time.time() for i in list: doit1(i)
要醬紫:
x = 0 def doit2(list): global x for i in list: x = x + i list = range(100000) t = time.time() doit2(list)
第三招:蛇之狙擊:高速搜索
這一招部分來(lái)源于IBM:Python 代碼性能優(yōu)化技巧,搜索算法的最高境界是O(1)的算法復(fù)雜度。也就是 Hash Table。本人幸本科的時(shí)候?qū)W了點(diǎn)數(shù)據(jù)結(jié)構(gòu)。知道 Python 的 list 使用類似鏈表的方法實(shí)現(xiàn)的。如過(guò)列表很大的話,在茫茫多的項(xiàng)里面用 if X in list_a 來(lái)做搜索和判斷效率是非常低的。
Python 的 tuple 我用得非常少,不評(píng)論。另兩個(gè)我用得非常多的是 set 和 dict。這兩個(gè)就是用的類似 Hash Table 的實(shí)現(xiàn)方法。
所以盡量不要醬紫:
k = [10,20,30,40,50,60,70,80,90] for i in xrange(10000): if i in k: #Do something continue
要醬紫:
``` k = [10,20,30,40,50,60,70,80,90] k_dict = {i:0 for i in k}
先把 list 轉(zhuǎn)換成 dictionary
for i in xrange(10000): if i in k_dict: #Do something continue ```
找 list 的交集,不要醬紫:
list_a = [1,2,3,4,5] list_b = [4,5,6,7,8] list_common = [a for a in list_a if a in list_b]
要醬紫:
list_a = [1,2,3,4,5] list_b = [4,5,6,7,8] list_common = set(list_a)&set(list_b)
第四招:小蛇蛇……:想不出來(lái)名字了,就是各種小 Tips
變量交換不需要中間變量:a,b = b,a (這里有個(gè)神坑,至今記憶深刻:True,False = False,True)
如果使用 Python2.x,用 xrange 代替 range,如果用 Python3.x,range 已經(jīng)是 xrange 了,xrange 已經(jīng)木有了。xrange 不會(huì)像 range 一樣生成一個(gè)列表,而是生成一個(gè)迭代器,省內(nèi)存。
可以用 x>y>z 代替 x>y and y>z。效率更高,可讀性也更好。當(dāng)然理論上 x>y
add(x,y) 一般會(huì)比 a+b 要快?這個(gè)本人有所懷疑,實(shí)驗(yàn)了一下,首先 add 不能直接用,要 import operator,第二,我的實(shí)驗(yàn)結(jié)果表示 add(x,y) 完全沒(méi)有 a+b 快,更何況還要犧牲可讀性。
while 1 確實(shí)比 while True 要快那么一點(diǎn)點(diǎn)。做了兩次實(shí)驗(yàn),大概快了15%左右。
第五招:無(wú)蛇勝有蛇:代碼之外的性能
代碼之外嘛,除了硬件之外,就是編譯器了,這里隆重推薦 pypy。pypy是一種叫做 just-in-time 的即時(shí)編譯器。這個(gè)編譯器的特點(diǎn)就是編譯一句跑一句,和靜態(tài)的編譯器的區(qū)別嘛,我在知乎上看到一個(gè)非常形象的比喻:
假定你是一個(gè)導(dǎo)演,靜態(tài)編譯就是讓演員把整個(gè)劇本背下來(lái)吃透,然后連續(xù)表演一個(gè)小時(shí)。動(dòng)態(tài)編譯就是讓演員表演兩分鐘,然后思考一下,再看一下劇本,再表演兩分鐘……
動(dòng)態(tài)編譯和靜態(tài)編譯各有所長(zhǎng),看你演的是電影還是話劇了。
此外還有一個(gè) Cython 可以在 python 里內(nèi)置一些 C 的代碼。我用的非常少,但是關(guān)鍵時(shí)刻確實(shí)有效。
相關(guān)文章
詳解Python2.x中對(duì)Unicode編碼的使用
這篇文章主要介紹了詳解Python2.x中對(duì)Unicode編碼的使用,Python3中Unicode被作為默認(rèn)的編碼來(lái)使用,而在目前仍被廣泛應(yīng)用的Python2的版本中Unicode卻是一個(gè)在使用中需要注意的地方,需要的朋友可以參考下2015-04-04Python發(fā)送form-data請(qǐng)求及拼接form-data內(nèi)容的方法
這篇文章主要介紹了Python發(fā)送form-data請(qǐng)求及拼接form-data內(nèi)容的方法,文中采用的是requests的方式發(fā)送multipart/form-data請(qǐng)求,需要的朋友可以參考下2016-03-03python中threading超線程用法實(shí)例分析
這篇文章主要介紹了python中threading超線程用法,實(shí)例分析了Python中threading模塊的相關(guān)使用技巧,需要的朋友可以參考下2015-05-05淺談Python中用datetime包進(jìn)行對(duì)時(shí)間的一些操作
下面小編就為大家?guī)?lái)一篇淺談Python中用datetime包進(jìn)行對(duì)時(shí)間的一些操作。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06一篇文章弄懂Python中的可迭代對(duì)象、迭代器和生成器
這篇文章主要給大家介紹了關(guān)于Python中可迭代對(duì)象、迭代器和生成器的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Python具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08解決python明明pip安裝成功卻找不到包的問(wèn)題
今天小編就為大家分享一篇解決python明明pip安裝成功卻找不到包的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08