python編碼最佳實踐之總結

1、數據結構的選擇:

     1. 在列表中查找:python

   對於已經排序的列表考慮用bisect模塊來實現查找元素,該模塊將使用二分查找實現git

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來進行,平時使用的時候多注意下,特別當列表比較大的時候,性能的影響就更大。app

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

collections有三種類型:

  1. deque:加強功能的相似list類型
  2. defaultdict:相似dict類型
  3. 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提倡簡單、優雅。因此正確的語法實踐又頗有必要,這樣纔會寫出優雅易於閱讀的代碼。

2、語法最佳實踐:

  1. 字符串操做:優於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

    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 time
import hashlib
import pickle
from itertools import chain
cache = {}
def is_obsolete(entry, duration):
    return time.time() - entry['time'] > duration

def 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 + c

print 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 + b

print very_very_complex_stuff(2, 2)
time.sleep(2)
print very_very_complex_stuff(2, 2)
 

運行結果:

6

we got a winner

6

4

4

裝飾器在不少場景用到,好比參數檢查、鎖同步、單元測試框架等,有興趣的人能夠本身進一步學習。

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

 

3、 編碼小技巧:

  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的實現方法。

相關文章
相關標籤/搜索