python 提高效率的幾個小技巧

1.1. 最常見
一個最常見的速度陷坑(至少是俺在沒看到網上這篇介紹時陷進去
過好些次的) 是: 許多短字串併成長字串時, 大家通常會用:

Toggle line numbers
   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
模塊:

Toggle line numbers
   1 import profile
   2 profile.run (’想要檢查的函數名()’)
就會打印出那個函數裏調用了幾次其它函數, 各用了多少時間, 總共用了多少時間等信息 — Nice ? 詳請參閱<<庫參考>>中的 profile模塊的論述.

當然腦袋笨一點或是聰明一點的, 也可以用time模塊中的time() 來顯示系統時間, 減去上次的time()就是與它的間隔秒數了.

1.1.2. 字串相併
就頭上的例子而言, 用 :

Toggle line numbers
   1 longStr =”.join(shortStrs)
立馬搞定, 但如果shortStrs裏面不都是字串, 而包含了些數 字呢 ? 直接用join就會出錯. 不怕, 這樣來:

Toggle line numbers
   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', ...]中的每個字串變成大寫, 可能會用:

Toggle line numbers
   1 import string
   2 newL = []
   3 for s in l: newL.append( string.upper(s) )
用map就可以省去for循環的前頭:

Toggle line numbers
   1 import string
   2 newL = map (string.upper, l)
Guido的文章講得很詳細.

1.1.5. 局域變量 及 ‘.’
象上面, 若用 append = newL.append, 及換種import方法:

Toggle line numbers
   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', ...] 中每個詞出現的次數, 你可能會:

Toggle line numbers
   1 count = {}
   2 for s in l:
   3     if not count.has_key(s): count[s] = 0
   4     else: count[s] += 1
由於每次都得在count中尋找是否已有同名關鍵詞, 會很費時間. 而用try:

Toggle line numbers
   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)比較重, 所以處理大量 數據時, 應:

Toggle line numbers
   1 def f():
   2 for d in hugeData: …
   3 f()
而不要:

Toggle line numbers
   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. 我們自個兒的體悟
請有心得者分享!

在“大量數據處理”小節裏,是不是說,不要再循環體內部調用函數,應該把函數放到外面?從Python2.2開始,”找出速度瓶頸”,已經可以使用hotshot模塊了.據說對程序運行效率的影響要比profile小. — jacobfan
“由於Python 中的函數調用前頭(overhead)比較重, 所以處理大量 數據時, 應: ” 這句譯文中,overhead翻譯成”前頭”好象不妥.翻譯成”由於Python 中函數調用的開銷比較大,…”要好些 — jacobfan
數組排序中講的方法真的會快點嗎? 真的快到我們值得放棄直接用sort得到得可讀性嗎?值得懷疑 — hoxide
Python2.4以後 sort和sorted的使用更加靈活,link已經加到文中,我沒有比較過效率。-yichun
關於 “try的使用”:
其實setdefault方法就是爲這個目的設的:

Toggle line numbers
   1 count = {}
   2 for s in l:
   3     count.setdefault(s, 0) += 1
這個其實能做更多。通常遇到的問題是要把類似的東西group起來,所以你可能想用:

Toggle line numbers
   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’)))

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/guzicheng/archive/2010/10/13/5939222.aspx

 

 

說明:增加代碼的描述力,可以成倍減少你的LOC,做到簡單,並且真切有力
觀點:少打字=多思考+少出錯,10代碼行比50行更能讓人明白,以下技巧有助於提高5倍工作效率

1. 交換變量值時避免使用臨時變量:(cookbook1.1)
老代碼:我們經常很熟練於下面的代碼
temp = x
x = y
y = temp
代碼一:
u, v, w = w, v, u  
有人提出可以利用賦值順序來簡化上面的三行代碼成一行
代碼二:
u, v = v, u  
其實利用Python元組賦值的概念,可更簡明 -- 元組初始化 + 元組賦值

2. 讀字典時避免判斷鍵值是否存在:(cookbook1.2)
d = { 'key': 'value' }
老代碼:
if 'key' in d: print d['key']
else: print 'not find'
新代碼:
print d.get('key', 'not find')   

3. 尋找最小值和位置的代碼優化
s = [ 4,1,8,3 ]
老代碼:
mval, mpos = MAX, 0
for i in xrange(len(s)):
    if s[i ]
觀點一:用Python編程,需要有“一字千金”的感覺;既然選擇了Python,就不要在意單條語句的效率。
上面幾點例子很基礎,實際中將原始代碼壓縮1/5並不是不可能,我們之前一個子項目,C++代碼270K,重構後Python代碼只有67K,當然使用python的日誌模塊(logging),讀寫表格文本(csv)等,也功不可末,最終代碼變成原來的1/4,我覺得自己的壽命延長了三倍。。。下面優化幾個常用代碼:

4. 文件讀取工作的最簡單表達:
老代碼:我們需要將文本文件讀入到內存中
line = ''
fp = open('text.txt', 'r')
for line in fp: text += line
代碼一:
text = string.join([ line for line in open('text.txt')], '']
代碼二:
text = ''.join([ line for line in open('text.txt')])   
代碼三:
text = file('text.txt').read()  
新版本的Python可以讓你寫出比1,2漂亮的代碼(open是file的別名,這裏file更直觀)

5. 如何在Python實現三元式:
老代碼:用慣C++,Java,C#不喜歡寫下面代碼
if n >= 0: print 'positive'
else: print 'negitive'
代碼一:該技巧在 Lua裏也很常見
print (n >= 0) and 'positive' or 'negitive'
說明:這裏的'and'和'or'相當於C中的':'和'?'的作用,道理很簡單,因爲如果表達式爲真了那麼後面的or被短路,取到'positive';否則,and被短路,取到'negitive'
代碼二:
print (n >= 0 and ['positive'] or ['negitive])[0]
說明:將兩個值組裝成元組,即使'positive'是None, '', 0 之類整句話都很安全
代碼三:
print ('negitive', 'positive')[n >= 0]
說明:(FalseValue, TrueValue)[Condition] 是利用了 元組訪問 + True=1 兩條原理

6. 避免字典成員是複雜對象的初始化:(cookbook1.5)
老代碼:
if not y in d: d[y] = { }
d[y][x] = 3
新代碼:
d.setdefault(y, { })[x] = 3
如果成員是列表的話也一樣: d.setdefault(key, []).append(val)
上面六點技巧加以發揮,代碼已經很緊湊了,但是還沒有做到“沒有一句廢話”可能有人懷疑真的能減少1/5的代碼麼??我要說的是1/5其實很保守,Thinking in C++的作者後來用了Python以後覺得Python甚至提高了10倍的工作效率。下面的例子可以進一步說明:
例子1:把文本的IP地址轉化爲整數
說明:需要將類似'192.168.10.214'的IP地址轉化爲 0x0C0A80AD6,在不用 inet_aton情況下。當C++/Java程序員正爲如何進行文本分析,處理各種錯誤輸入煩惱時,Python程序員已經下班:
f = lambda ip: sum( [ int(k)*v for k, v in zip(ip.split('.'), [1
首先ip.split('.')得到列表['192','168','10','214'],經過zip一組裝,就變成
[('192',0x1000000),('168',0x10000),('10',0x100),('214',1)]
接着for循環將各個元組的兩項做整數乘法,最後將新列表的值用sum求和,得到結果
C++程序員不肖道:“你似乎太相信數據了,根本沒有考慮道錯誤的輸入”
Python程序員回答:“外面的try/except已幫我完成所有異常處理,不必擔心越界崩潰而無法捕獲”
Java程序員得意的看着自己百行代碼:“我想知道你如何讓你的同事來理解你的傑作?你有沒有考慮過將類似gettoken之類的功能獨立處理,讓類似問題可以複用?我的代碼說明了如何充分發揮Reflection和interface的優秀特性,在增加重用性的同時,提供清晰可讀的代碼”
Python無奈道:“這是‘純粹的代碼’,意思是不可修改,類似正則表達式,只要讓人明白他的功能就行了,要修改就重寫。再我能用三行代碼完成以內絕不會有封裝的想法,況且熟悉Python者也不覺得難讀啊?”
C++程序員拋出殺手簡:“如果讓你一秒鐘處理10w個ip轉化的話怎麼辦?”
Python程序員覺得想睡覺:“你覺得我會蠢到還用Python做這樣的事情麼?”
此時C++程序員似乎並沒聽到,反而開始認真的思考起自己剛纔提出問題來,一會只見他輕藐的看了另外兩人一眼,然後胸有成竹的轉到電腦前,開始往屏幕上輸入: “template <....”
小笑話:封裝的陷阱,讓人一邊喊着“封裝”或“複用”,一邊在新項目中,全部打破重寫,並解釋爲--重構
觀點二:簡單即是美,把一個東西設計複雜了,本身就是有問題的
思考題:上面的程序,如果反過來,將ip的整數形式轉化爲字符串,各位該如何設計呢??
例子2:輸出一個對象各個成員的名稱和值
g = lambda m: '\n'.join([ '%s=%s'%(k, repr(v)) for k, v in m.__dict__.iteritems() ])
用法:print g(x)
延伸:上面兩個例子熟悉了lambda以後,建議可以嘗試使用下 yield

觀點總結
Q:“怎樣纔算做到注重What you think多於What you are writing”
A:“就是說你手上打着第1頁需求的代碼,眼睛卻在看着第2頁需求的內容,心裏想着如何應對5-10頁的東西”
國外多年前廢除PASCAL改用Python做科研教學是有道理的,關於精簡代碼的例子舉不勝舉,用它編碼時應該有“一字千金”的感覺,否則最終寫出來的,還是“僞裝成Python的C++程序”。
編程本來就是快樂的,避免過多的體力勞動,贏得更多思考的時間。
思考題:到底是封裝呢?還是放棄封裝?
思考題:“more than one way to do it”是不是就是好事?它的反面是什麼?

轉自:http://www.cnblogs.com/yd1227/archive/2010/11/23/1885186.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章