python編碼最佳實踐之總結
程序員大咖
點擊右側關注,免費進階高級!
作者|xiaoqb
https://yq.aliyun.com/articles/2758
相信用python的同學不少,本人也一直對python情有獨鍾,毫無疑問python作為一門解釋性動態語言沒有那些編譯型語言高效,但是python簡潔、易讀以及可擴展性等特性使得它大受青睞。
工作中很多同事都在用python,但往往很少有人關注它的性能和慣用法,一般都是現學現用,畢竟python不是我們的主要語言,我們一般只是使用它來做一些系統管理的工作。但是我們為什麼不做的更好呢?python zen中有這樣一句:There should be one— and preferably only one —obvious way to do it. Although that way may not be obvious at first unless you』re Dutch. 大意就是python鼓勵使用一種最
優的方法去完成一件事,這也是和ruby等的一個差異。所以一種好的python編寫習慣個人認為很重要,本文就重點從性能角度出發對python的一些慣用法做一個簡單總結,希望對大家有用~
提到性能,最容易想到的是降低複雜度,一般可以通過測量代碼迴路複雜度(cyclomatic complexitly)和Landau符號(大O)來分析, 比如dict查找是O(1),而列表的查找卻是O(n),顯然數據的存儲方式選擇會直接影響演算法的複雜度。
數據結構的選擇
在列表中查找
對於已經排序的列表考慮用bisect模塊來實現查找元素,該模塊將使用二分查找實現
def
find
(seq, el)
: pos = bisect(seq, el)
if
pos ==0
or
( pos == len(seq)and
seq[-1
] != el ) :return
-1
return
pos -1
而快速插入一個元素可以用:
bisect.insort(list, element)
這樣就插入元素並且不需要再次調用 sort() 來保序,要知道對於長list代價很高.
set代替列表
比如要對一個list進行去重,最容易想到的實現:
seq = [
"a"
,"a"
,"b"
]res = []
for
iin
seq:if
inot
in
res:res.append(i)
顯然上面的實現的複雜度是O(n2),若改成:
seq = [
"a"
,"a"
,"b"
]res = set(seq)
複雜度馬上降為O(n),當然這裡假定set可以滿足後續使用。
另外,set的union,intersection,difference等操作要比列表的迭代快的多,因此如果涉及到求列表交集,並集或者差集等問題可以轉換為set來進行,平時使用的時候多注意下,特別當列表比較大的時候,性能的影響就更大。
使用python的collections模塊替代內建容器類型
collections有三種類型:
deque:增強功能的類似list類型
defaultdict:類似dict類型
namedtuple:類似tuple類型
列表是基於數組實現的,而deque是基於雙鏈表的,所以後者在中間or前面插入元素,或者刪除元素都會快很多。
defaultdict為新的鍵值添加了一個默認的工廠,可以避免編寫一個額外的測試來初始化映射條目,比dict.setdefault更高效,引用python文檔的一個例子:
使用profile stats工具進行性能分析
>>>
from
pbp.scripts.profilerimport
profile, stats>>>
s = [("yellow"
,1
), ("blue"
,2
), ("yellow"
,3
),...
("blue"
,4
), ("red"
,1
)]>>> @profile("defaultdict")
...
def
faster
()
:...
d = defaultdict(list)...
for
k, vin
s:...
d[k].append(v)...
>>> @profile("dict")
...
def
slower
()
:...
d = {}...
for
k, vin
s:...
d.setdefault(k, []).append(v)...
>>>
slower(); faster()Optimization: Solutions
[
306
]>>>
stats["dict"
]{
"stones"
:16.587882671716077
,"memory"
:396
,"time"
:0.35166311264038086
}>>>
stats["defaultdict"
]{
"stones"
:6.5733464259021686
,"memory"
:552
,"time"
:0.13935494422912598
}可見性能提升了快3倍。defaultdict用一個list工廠作為參數,同樣可用於內建類型,比如long等。
除了實現的演算法、架構之外,python提倡簡單、優雅。所以正確的語法實踐又很有必要,這樣才會寫出優雅易於閱讀的代碼。
語法最佳實踐
字元串操作:優於python字元串對象是不可改變的,因此對任何字元串的操作如拼接,修改等都將產生一個新的字元串對象,而不是基於原字元串,因此這種持續的 copy會在一定程度上影響Python的性能:
(1)用join代替 『+』 操作符,後者有copy開銷;
(2)同時當對字元串可以使用正則表達式或者內置函數來處理的時候,選擇內置函數。如str.isalpha(),str.isdigit(),str.startswith((『x』, 『yz』)),str.endswith((『x』, 『yz』))
(3)字元格式化操作優於直接串聯讀取:
str =
"%s%s%s%s"
% (a, b, c, d)# efficient
str =
""
+ a + b + c + d +""
# slow
善用list comprehension(列表解析) & generator(生成器) & decorators(裝飾器),熟悉itertools等模塊:
(1) 列表解析,我覺得是python2中最讓我印象深刻的特性,舉例1:
>>>
# the following is not so Pythonic
>>>
numbers = range(10
)>>>
i =0
>>>
evens = []>>>
while
i < len(numbers):>>>
if
i %2
==0
: evens.append(i)>>>
i +=1
>>>
[0
,2
,4
,6
,8
]>>>
# the good way to iterate a range, elegant and efficient
>>>
evens = [ ifor
iin
range(10
)if
i%2
==0
]>>>
[0
,2
,4
,6
,8
]舉例2:
def
_treament
(pos, element)
:return
"%d: %s"
% (pos, element)f = open(
"test.txt"
,"r"
)if
__name__ =="__main__"
:#list comps 1
for
linein
ffor
wordin
line.split())#list comps 2
1
, y +1
)for
xin
range(3
)for
yin
range(4
)]#func
lambda
x: x %2
==0
, range(10
))#list comps3
for
iin
range(10
)if
i %2
==0
]#list comps4 pythonic
for
i, elin
enumerate(range(10
))]output:
24
[(
1
,1
), (1
,2
), (1
,3
), (1
,4
), (2
,1
), (2
,2
), (2
,3
), (2
,4
), (3
,1
), (3
,2
), (3
,3
), (3
,4
)][
0
,2
,4
,6
,8
][
0
,2
,4
,6
,8
][
"0: 0"
,"1: 1"
,"2: 2"
,"3: 3"
,"4: 4"
,"5: 5"
,"6: 6"
,"7: 7"
,"8: 8"
,"9: 9"
]沒錯,就是這麼優雅簡單。
(2) 生成器表達式在python2.2引入,它使用』lazy evaluation』思想,因此在使用內存上更有效。引用python核心編程中計算文件中最長的行的例子:
f = open(
"/etc/motd, "r")
longest = max(len(x.strip()) for x in f)
f.close()
return longest
這種實現簡潔而且不需要把文件文件所有行讀入內存。
(3) python在2.4引入裝飾器,又是一個讓人興奮的特性,簡單來說它使得函數和方法封裝(接收一個函數並返回增強版本的函數)更容易閱讀、理解。』@』符號是裝飾器語法,你可以裝飾一個函數,記住調用結果供後續使用,這種技術被稱為memoization的,下面是用裝飾器完成一個cache功能:
import
timeimport
hashlibimport
picklefrom
itertoolsimport
chaincache = {}
def
is_obsolete
(entry, duration)
:return
time.time() - entry["time"
] > durationdef
compute_key
(function, args, kw)
:#序列化/反序列化一個對象,這裡是用pickle模塊對函數和參數對象進行序列化為一個hash值
key = pickle.dumps((function.func_name, args, kw))
#hashlib是一個提供MD5和sh1的一個庫,該結果保存在一個全局字典中
return
hashlib.sha1(key).hexdigest()def
memoize
(duration=
10
):def
_memoize
(function)
:def
__memoize
(*args, **kw)
:key = compute_key(function, args, kw)
# do we have it already
if
(keyin
cacheand
not
is_obsolete(cache[key], duration)):"we got a winner"
return
cache[key]["value"
]
# computing
result = function(*args, **kw)
# storing the result
cache[key] = {
"value"
: result,-"time"
: time.time()}return
resultreturn
__memoizereturn
_memoize@memoize()
def
very_very_complex_stuff
(a, b, c)
:return
a + b + c2
,2
,2
)2
,2
,2
)@memoize(1)
def
very_very_complex_stuff
(a, b)
:return
a + b2
,2
)time.sleep(
2
)2
,2
)運行結果:
6
we got a winner
6
4
4
裝飾器在很多場景用到,比如參數檢查、鎖同步、單元測試框架等,有興趣的人可以自己進一步學習。
善用python強大的自省能力(屬性和描述符):自從使用了python,真的是驚訝原來自省可以做的這麼強大簡單,關於這個話題,限於內容比較多,這裡就不贅述,後續有時間單獨做一個總結,學習python必須對其自省好好理解。
編碼小技巧
在python3之前版本使用xrange代替range,因為range()直接返回完整的元素列表而xrange()在序列中每次調用只產生一個整數元素,開銷小。(在python3中xrange不再存在,裡面range提供一個可以 遍歷任意長度的範圍的iterator)
if done is not None比語句if done != None更快;
盡量使用」in」操作符,簡潔而快速: for i in seq: print i
『x < y < z』代替』x < y and y < z』;
while 1要比while True更快, 因為前者是單步運算,後者還需要計算;
盡量使用build-in的函數,因為這些函數往往很高效,比如add(a,b)要優於a+b;
在耗時較多的循環中,可以把函數的調用改為內聯的方式,內循環應該保持簡潔。
使用多重賦值來swap元素:
x, y = y, x
# elegant and efficient
而不是:
temp = x x = y y = temp
三元操作符(python2.5後):V1 if X else V2,避免使用(X and V1) or V2,因為後者當V1=」」時,就會有問題。
python之switch case實現:因為switch case語法完全可用if else代替,所以python就沒 有switch case語法,但是我們可以用dictionary或lamda實現:
switch case結構:
switch (var)
{
case v1: func1();
case v2: func2();
...
case vN: funcN();
default: default_func();
}
dictionary實現:
values = {
v1: func1,
v2: func2,
...
vN: funcN,
}
values.get(var, default_func)()
lambda實現:
{
"1"
:lambda
: func1,"2"
:lambda
: func2,"3"
:lambda
: func3}[value]()
用try…catch來實現帶Default的情況,個人推薦使用dict的實現方法。
這裡只總結了一部分python的實踐方法,希望這些建議可以幫助到每一位使用python的同學,優化性能不是重點,高效解決問題,讓自己寫的代碼更加易於維護,更加pythonic!
【點擊成為源碼大神】
![](https://pic.pimg.tw/zzuyanan/1488615166-1259157397.png)
![](https://pic.pimg.tw/zzuyanan/1482887990-2595557020.jpg)
※Python裝飾器的誕生過程
※製作安裝在手機上的爬蟲軟體,python在電腦端爬取小說算什麼?
TAG:Python開發 |