python編碼最佳實務之總結

來源:互聯網
上載者:User
相信用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),顯然資料的儲存方式選擇會直接影響演算法的複雜度。

一、資料結構的選擇
1. 在列表中尋找:

對於已經排序的列表考慮用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代價很高.

2. set代替列表:

比如要對一個list進行去重,最容易想到的實現:

seq = ['a', 'a', 'b']res = []for i in seq:  if i not in res:    res.append(i)

顯然上面的實現的複雜度是O(n2),若改成:

seq = ['a', 'a', 'b']res = set(seq)

複雜度馬上降為O(n),當然這裡假定set可以滿足後續使用。

另外,set的union,intersection,difference等操作要比列表的迭代快的多,因此如果涉及到求列表交集,並集或者差集等問題可以轉換為set來進行,平時使用的時候多注意下,特別當列表比較大的時候,效能的影響就更大。

3. 使用python的collections模組替代內建容器類型:

collections有三種類型:

deque:增強功能的類似list類型
defaultdict:類似dict類型
namedtuple:類似tuple類型

列表是基於數組實現的,而deque是基於雙鏈表的,所以後者在中間or前面插入元素,或者刪除元素都會快很多。

defaultdict為新的索引值添加了一個預設的工廠,可以避免編寫一個額外的測試來初始化映射條目,比dict.setdefault更高效,引用python文檔的一個例子:

#使用profile stats工具進行效能分析>>> from pbp.scripts.profiler import profile, stats>>> s = [('yellow', 1), ('blue', 2), ('yellow', 3),... ('blue', 4), ('red', 1)]>>> @profile('defaultdict')... def faster():... d = defaultdict(list)... for k, v in s:... d[k].append(v)...>>> @profile('dict')... def slower():... d = {}... for k, v in 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)同時當對字串可以使用Regex或者內建函數來處理的時候,選擇內建函數。如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

2. 善用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 = [ i for i in 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  print sum(len(word) for line in f for word in line.split())  #list comps 2  print [(x + 1, y + 1) for x in range(3) for y in range(4)]  #func  print filter(lambda x: x % 2 == 0, range(10))  #list comps3  print [i for i in range(10) if i % 2 == 0]  #list comps4 pythonic  print [_treament(i, el) for i, el in 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 itertools import 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 (key in cache and        not is_obsolete(cache[key], duration)):        print 'we got a winner'        return cache[key]['value']      # computing      result = function(*args, **kw)      # storing the result      cache[key] = {'value': result,-              'time': time.time()}      return result    return __memoize  return _memoize@memoize()def very_very_complex_stuff(a, b, c):  return a + b + cprint very_very_complex_stuff(2, 2, 2)print very_very_complex_stuff(2, 2, 2)@memoize(1)def very_very_complex_stuff(a, b):  return a + bprint very_very_complex_stuff(2, 2)time.sleep(2)print very_very_complex_stuff(2, 2)

運行結果:

6we got a winner644

裝飾器在很多情境用到,比如參數檢查、鎖同步、單元測試架構等,有興趣的人可以自己進一步學習。

3. 善用python強大的自省能力(屬性和描述符):自從使用了python,真的是驚訝原來自省可以做的這麼強大簡單,關於這個話題,限於內容比較多,這裡就不贅述,後續有時間單獨做一個總結,學習python必須對其自省好好理解。

三、 編碼小技巧
1、在python3之前版本使用xrange代替range,因為range()直接返回完整的元素列表而xrange()在序列中每次調用只產生一個整數元素,開銷小。(在python3中xrange不再存在,裡面range提供一個可以 遍曆任意長度的範圍的iterator)
2、if done is not None比語句if done != None更快;
3、盡量使用"in"操作符,簡潔而快速: for i in seq: print i
4、'x < y < z'代替'x < y and y < z';
5、while 1要比while True更快, 因為前者是單步運算,後者還需要計算;
6、盡量使用build-in的函數,因為這些函數往往很高效,比如add(a,b)要優於a+b;
7、在耗時較多的迴圈中,可以把函數的調用改為內聯的方式,內迴圈應該保持簡潔。
8、使用多重賦值來swap元素:

x, y = y, x # elegant and efficient

而不是:

temp = x
x = y
y = temp

9. 三元操作符(python2.5後):V1 if X else V2,避免使用(X and V1) or V2,因為後者當V1=""時,就會有問題。

10. 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的同學,最佳化效能不是重點,高效解決問題,讓自己寫的代碼更加易於維護!

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.