1. Python編程速度技巧
1.1. 最常見
* 一個最常見的速度陷坑(至少是俺在沒看到網上這篇介紹時陷進去
過好些次的) 是: 許多短字串並成長字串時, 大家通常會用:
切換行號顯示
1 shortStrs = [ str0, str1, ..., strN]
2
N+1個字串所組成的數列
3 longStr = ''
4 for s in shortStrs: longStr += s
因為Python裡字串是不可變的, 所以每次 longStr += s 都是將原 來的 longStr 與 str 拷貝成一個新字串, 再賦給longStr. 隨著 longStr的不斷增長, 所要拷貝的內容越來越長. 最後導至str0被 拷貝N+1次, str1是N次, ... .
那咋辦呢 ? 咱們來看看Skip Montanaro先生的解說: http://musi-cal.mojam.com/~skip/python/fastpython.html 及可參考一下Guido van Rossum本人的:http://www.python.org/doc/essays/list2str.html
1.1.1. 找出速度瓶頸
* 1)首先在大家應先學會怎麼去找出速度瓶頸: Python內建有profile
模組:
切換行號顯示
1 import profile
2 profile.run ('想要檢查的函數名()')
就會列印出那個函數裡調用了幾次其它函數, 各用了多少時間, 總共用了多少時間等資訊 --- Nice ? 詳請參閱<<庫參考>>中的 profile模組的論述.
當然腦袋笨一點或是聰明一點的, 也可以用time模組中的time() 來顯示系統時間, 減去上次的time()就是與它的間隔秒數了.
1.1.2. 字串相併
* 就頭上的例子而言, 用 :
切換行號顯示
1 longStr =''.join(shortStrs)
立馬搞定, 但如果shortStrs裡面不都是字串, 而包含了些數 字呢 ? 直接用join就會出錯. 不怕, 這樣來:
切換行號顯示
1 shortStrs = [str(s) for s in shortStrs[i]]
2 longStr = ''.join(shortStrs)
也即先將數列中所有內容都轉化為字串, 再用join.
對少數幾個字串相併, 應避免用: all = str0 + str1 + str2 + str3 而用: all = '%s%s%s%s' % (str0, str1, str2, str3)
1.1.3. 數列排序
* list.sort ()
你可以按特定的函數來: list.sort( 函數 ), 只要這個函數接受 兩參數, 並按特定規則返回1, 0, -1就可以. --- 很方便吧? 但 會大大減慢運行速度. 下面的方法, 俺舉例子來說明可能更容易 明白.
比方說你的數列是 l = ['az', 'by'], 你想以第二個字母來排序. 先取出你的關鍵詞, 並與每個字串組成一個元組: new = map (lambda s: (s[1], s), l )
於是new變成[('z', 'az'), ('y', 'by')], 再把new排一下序: new.sort()
則new就變成 [('y', 'by'), ('z', 'az')], 再返回每個元組中 的第二個字串: sorted = map (lambda t: t[1], new)
於是sorted 就是: ['by', 'az']了. 這裡的lambda與map用得很 好.
*
Python2.4以後, sort和sorted的使用可以參考這片 Wiki: HowToSort
1.1.4. 迴圈
比如for迴圈. 當迴圈體很簡單時, 則迴圈的調用前頭(overhead) 會顯得很臃腫, 此時map又可以幫忙了. 比如你想把一個長數列 l=['a', 'b', ...]中的每個字串變成大寫, 可能會用:
切換行號顯示
1 import string
2 newL = []
3 for s in l: newL.append( string.upper(s) )
用map就可以省去for迴圈的前頭:
切換行號顯示
1 import string
2 newL = map (string.upper, l)
Guido的文章講得很詳細.
1.1.5. 局域變數 及 '.'
象上面, 若用 append = newL.append, 及換種import方法:
切換行號顯示
1 import string
2 append = newL.append
3 for s in l: append (string.upper(s))
會比在for中運行newL.append快一些, 為啥? 局域變數容易尋找.
俺自己就不比較時間了, Skip Montanaro的結果是:
基本迴圈: 3.47秒
去點用局域變數: 1.79秒
使用map: 0.54秒
1.1.6. try的使用
比如你想計算一個字串數列: l = ['I', 'You', 'Python', 'Perl', ...] 中每個詞出現的次數, 你可能會:
切換行號顯示
1 count = {}
2 for s in l:
3 if not count.has_key(s): count[s] = 0
4 else: count[s] += 1
由於每次都得在count中尋找是否已有同名關鍵詞, 會很費時間. 而用try:
切換行號顯示
1 count ={}
2 for s in l:
3 try: count[s] += 1
4 except KeyError: count[s] = 0
就好得多. 當然若經常出現例外時, 就不要用try了.
1.1.7. import語句
這好理解. 就是避免在函數定義中來import一個模組, 應全在 全域塊中來import
1.1.8. 大量資料處理
由於Python中的函數調用前頭(overhead)比較重, 所以處理大量 資料時, 應:
切換行號顯示
1 def f():
2 for d in hugeData: ...
3 f()
而不要:
切換行號顯示
1 def f(d): ...
2 for d in hugeData: f(d)
這點好象對其它語言也適用, 差不多是放之四海而皆準, 不過對 解釋性語言就更重要了.
1.1.9. 減少周期性檢查
這是Python的本徵功能: 周期性檢查有沒有其它緒(thread)或系 統訊號(signal)等要處理.
可以用sys模組中的setcheckinterval 來設定每次檢查的時間間隔.
預設是10, 即每10個虛擬指令 (virtual instruction)檢查一次.
當你不用緒並且也懶得搭理 系統訊號時, 將檢查周期設長會增加速度, 有時還會很顯著.
---編/譯完畢. 看來Python是易學難精了, 象圍棋?
2. 我們自個兒的體悟
請有心得者分享!
2.1. 故事
*
Python效能調優 ~ flydudu 分享
2.2. 思考
* 在“大量資料處理”小節裡,是不是說,不要再迴圈體內部調用函數,應該把函數放到外面?從Python2.2開始,"找出速度瓶頸",已經可以使用hotshot模組了.據說對程式運行效率的影響要比profile小. -- jacobfan
* "由於Python中的函數調用前頭(overhead)比較重, 所以處理大量 資料時, 應: " 這句譯文中,overhead翻譯成"前頭"好象不妥.翻譯成"由於Python中函數調用的開銷比較大,..."要好些 -- jacobfan
* 數組排序中講的方法真的會快點嗎? 真的快到我們值得放棄直接用sort得到得可讀性嗎?值得懷疑 -- hoxide
* Python2.4以後 sort和sorted的使用更加靈活,link已經加到文中,我沒有比較過效率。-yichun
* 關於 “try的使用”:
其實setdefault方法就是為這個目的設的:
切換行號顯示
1 count = {}
2 for s in l:
3 count.setdefault(s, 0) += 1
這個其實能做更多。通常遇到的問題是要把類似的東西group起來,所以你可能想用:
切換行號顯示
1 count = {}
2 for s in l:
3 count.setdefault(s, []).append(s)
但是這樣你只能把同樣的東西hash起來,而不是一類東西。比如說你有一個dict構成的list叫sequence,需要按這些dict的某個key value分類,你還要對分類後的每個類別裡面的這些dict各作一定的操作,你就需要用到Raymond實現的這個groupby,你就可以寫:
totals = dict((key, group)
for key, group in groupby(sequence, lambda x: x.get('Age')))
- yichun
* shortStrs = [str(s) for s in shortStrs[i]]這句我在python2.5下報錯(i未定義),我改成shortStrs = [str(s) for s in shortStrs]就可以了。