Python編程規範及性能優化html
全部的 Python 腳本文件都應在文件頭標上 # -*- coding:utf-8 -*- 。設置編輯器,默認保存爲 utf-8 格式。python
業界廣泛認同 Python 的註釋分爲兩種的概念,一種是由 # 開頭的「真正的」註釋,另外一種是 docstrings。前者代表爲什麼選擇當前實現以及這種實現的原理和難點,後者代表如何使用這個包、模塊、類、函數(方法),甚至包括使用示例和單元測試。linux
堅持適當註釋原則。對不存在技術難點的代碼堅持不註釋,對存在技術難點的代碼必須註釋。但與註釋不一樣,推薦對每個包、模塊、類、函數(方法)寫 docstrings,除非代碼一目瞭然,很是簡單。git
Python 依賴縮進來肯定代碼塊的層次,行首空白符主要有兩種:tab 和空格,但嚴禁二者混用。若是使用 tab 縮進,設定 tab 爲 4 個空格。程序員
公司內部推薦使用 4 個空格的 tab 進行縮進。正則表達式
空格在 Python 代碼中是有意義的,由於 Python 的語法依賴於縮進,在行首的空格稱爲前導空格。在這一節不討論前導空格相關的內容,只討論非前導空格。非前導空格在 Python 代碼中沒有意義,但適當地加入非前導空格能夠增進代碼的可讀性。算法
1)在二元算術、邏輯運算符先後加空格:如 a = b + c;
2)在一元前綴運算符後不加空格,如 if !flg: pass;
3)「:」用在行尾時先後皆不加空格,如分枝、循環、函數和類定義語言;用在非行尾時兩端加空格,如 dict 對象的定義 d = {‘key’ : ’value’}。
4)括號(含圓括號、方括號和花括號)先後不加空格,如 do_something(arg1, arg2),而不是 do_something( arg1, arg2 );
5)逗號後面加一個空格,前面不加空格;express
適當的空行有利於增長代碼的可讀性,加空行能夠參考以下幾個準則:編程
1)在類、函數的定義間加空行;
2)在 import 不一樣種類的模塊間加空行;
3)在函數中的邏輯段落間加空行,即把相關的代碼緊湊寫在一塊兒,做爲一個邏輯段落,段落間以空行分隔;windows
一致的命名能夠給開發人員減小許多麻煩,而恰如其分的命名則能夠大幅提升代碼的可讀性,下降維護成本。
常量名全部字母大寫,由下劃線鏈接各個單詞,如
WHITE = 0XFFFFFF
THIS_IS_A_CONSTANT = 1
變量名所有小寫,由下劃線鏈接各個單詞,如
color = WHITE
this_is_a_variable = 1
不管是類成員變量仍是全局變量,均不使用 m 或 g 前綴。私有類成員使用單一下劃線前綴標識,多定義公開成員,少定義私有成員。
變量名不該帶有類型信息,由於 Python 是動態類型語言。如iValue、names_list、dict_obj 等都是很差的命名。
函數名的命名規則與變量名相同。
類名單詞首字母大寫,不使用下劃線鏈接單詞,也不加入 C、T 等前綴。如:
class ThisIsAClass(object):
pass
模塊名所有小寫,對於包內使用的模塊,能夠加一個下劃線前綴,如:
module.py
_internal_module.py
包的命名規範與模塊相同。
命名應當儘可能使用全拼寫的單詞,縮寫的狀況有以下兩種:
1)經常使用的縮寫,如 XML、ID等,在命名時也應只大寫首字母,如:
class XmlParser(object):pass
2)命名中含有長單詞,對某個單詞進行縮寫。這時應使用約定成俗的縮寫方式,如去除元音、包含輔音的首字符等方式,例如:
function 縮寫爲 fn
text 縮寫爲 txt
object 縮寫爲 obj
count 縮寫爲 cnt
number 縮寫爲 num,等。
主要是指 __xxx__ 形式的系統保留字命名法。項目中也可使用這種命名,它的意義在於這種形式的變量是隻讀的,這種形式的類成員函數儘可能不要重載。如:
class Base(object):
def __init__(self, id, parent = None):
self.__id__ = id
self.__parent__ = parent
def __message__(self, msgid):
# …略
其中 __id__、__parent__ 和 __message__ 都採用了系統保留字命名法。
import 語句有如下幾個原則須要遵照:
1)import 的次序,先 import Python 內置模塊,再 import 第三方模塊,最後 import 本身開發的項目中的其它模塊;這幾種模塊中用空行分隔開來。
2)一條 import 語句 import 一個模塊。
3)當從模塊中 import 多個對象且超過一行時,使用以下斷行法(此語法 py2.5 以上版本才支持):
from module import (obj1, obj2, obj3, obj4,obj5, obj6)
4)不要使用 from module import *,除非是 import 常量定義模塊或其它你確保不會出現命名空間衝突的模塊。
對於賦值語言,主要是不要作無謂的對齊,如:
a = 1 # 這是一個行註釋
variable = 2 # 另外一個行註釋
fn = callback_function # 仍是行註釋
沒有必要作這種對齊,緣由有兩點:一是這種對齊會打亂編程時的注意力,大腦要同時處理兩件事(編程和對齊);二是之後閱讀和維護都很困難,由於人眼的橫向視野很窄,把三個字段當作一行很困難,並且維護時要增長一個更長的變量名也會破壞對齊。直接這樣寫爲佳:
a = 1 # 這是一個行註釋
variable = 2 # 另外一個行註釋
fn = callback_function # 仍是行註釋
對於分枝和循環,有以下幾點須要注意的:
1)不要寫成一行,如:
if !flg: pass 和 for i in xrange(10): print i都不是好代碼,應寫成
if !flg:
pass
for i in xrange(10):
print i
注:本文檔中出現寫成一行的例子是由於排版的緣由,不得做爲編碼中不斷行的依據。
2)條件表達式的編寫應該足夠 pythonic,如如下形式的條件表達式是拙劣的:
if len(alist) != 0: do_something()
if alist != []: do_something()
if s != 「」: do_something()
if var != None: do_something()
if var != False: do_something()
上面的語句應該寫成:
if seq: do_somethin() # 注意,這裏命名也更改了
if var: do_something()
3)用得着的時候多使用循環語句的 else 分句,以簡化代碼。
對於項目中已有的代碼,可能由於歷史遺留緣由不符合本規範,應當看做能夠容忍的特例,容許存在;但不該在新的代碼中延續舊的風格。
對於第三方模塊,可能不符合本規範,也應看做能夠容忍的特例,容許存在;但不該在新的代碼中使用第三方模塊的風格。
tab 與空格混用的縮進是不可容忍的,在運行項目時應使用 –t 或 –tt 選項排查這種可能性存在。出現混用的狀況時,若是是公司開發的基礎類庫代碼,應當通知類庫維護人員修改;第三方模塊則能夠經過提交 patch 等方式敦促開發者修正問題。
開發人員每每在加入項目以前已經造成自有的編碼風格,加入項目後應以本規範爲準編寫代碼。特別是匈牙利命名法,由於帶有類型信息,並不適合 Python 編程,不該在 Python 項目中應用。
閱讀 Zen of Python,在Python解析器中輸入 import this. 一個犀利的Python新手可能會注意到"解析"一詞, 認爲Python不過是另外一門腳本語言. "它確定很慢!"
毫無疑問:Python程序沒有編譯型語言高效快速. 甚至Python擁護者們會告訴你Python不適合這些領域. 然而,YouTube已用Python服務於每小時4千萬視頻的請求. 你所要作的就是編寫高效的代碼和須要時使用外部實現(C/C++)代碼或外部第三方工具.
代碼優化可以讓程序運行更快,它是在不改變程序運行結果的狀況下使得程序的運行效率更高,根據 80/20 原則,實現程序的重構、優化、擴展以及文檔相關的事情一般須要消耗 80% 的工做量。優化一般包含兩方面的內容:減少代碼的體積,提升代碼的運行效率。
有不少方法能夠用來縮短程序的執和時間.記住,每當執行一個Python腳本之時,就會調用一個解釋器.於是爲了對此作補償,須要對代碼作點工做.這與Python是一種解釋性語言有很大關係,不過經過減小需加以分析的語句的數量,也會減小解釋器的總開銷.
順便說起,Python解釋器具備一個命令麼選項(-0表明optimize--優化),使得程序以不執行某些字節操做的方式加以執行.通常來講, 該選項用於去除節字碼中給出異常產生的行號的註釋,並不編譯doc字符串和一些其餘東西.該標誌不會給出太多的速度增益,並且它會使用程序難以調試.
取決於如何定義,解釋器花費或多或少的時間嘗試計算出它們的值.Python在嘗試斷定變量名時利用動態做用域規則進行處理.當它在代碼中找到一個 變量時,首先經過查看局部名空間字典考察該變量是否是一個局部變量.若是找到該變量,就抓取該變量的值.不然再在全局名字空間字典中進行搜索,若是須要, 還會搜索內置名字空間.所以,局部變量比其餘類型變量的搜索速度要快得多,於是得到它們的值也要快得多.局部變量搜索速度快是由於它們對應於數組中的下標 操做,而全局變量搜索則對應於散列表搜索.一個良好的優化方法是:若是在函數中使用了不少全局變量,把它們的值賦給局部變量可能會有很大幫助.
在一個腳本之中,只需一次導入一個外部模塊便可.所以,在代碼中不須要多個import語句.實際上,應該避免在程序中嘗試再導入模塊.根據以往的 經驗, 應該把全部的import語句放在程序頭的最開始部分.然而,對一個模塊屢次調用import不會真正形成問題,由於它只是一個字典查找.若是必需要對一 個外部模塊的某些特定屬性進行大量引用,開始編寫代碼以前,應該考慮將這些元素複製到單個變量中(固然,若是可能的話)--特另是若是引用在一個循環內部 進行.只要導入模塊,解釋器就查找該模塊的字節編譯版.若是未找到,它會自動對模塊進行字節編譯並生成.pyc文件.所以,當下次嘗試導入此模塊時,字節 編譯文件就在那裏.正如讀者所體會的那樣.pyc文件比常規.py文件執行起來快不少,由於它們在執行以前就已經經解釋器解釋過.這裏的建議是儘可能使用字 節編譯模塊.不管是否擁有.pyc文件Python代碼都以相同的速度執行.唯一的區別是若是存在.pyc文件,啓動將會有所加快.代碼的實際運行速度沒 有區別.
python 中的字符串對象是不可改變的,所以對任何字符串的操做如拼接,修改等都將產生一個新的字符串對象,而不是基於原字符串,所以這種持續的 copy 會在必定程度上影響 python 的性能。對字符串的優化也是改善性能的一個重要的方面,特別是在處理文本較多的狀況下。
1. 在字符串鏈接的使用盡可能使用 join() 而不是 +;
2. 當對字符串可使用正則表達式或者內置函數來處理的時候,選擇內置函數。如 str.isalpha(),str.isdigit(),str.startswith(('x', 'yz')),str.endswith(('x', 'yz'));
3. 對字符進行格式化比直接串聯讀取要快,所以要在字符串與其餘變量鏈接時就使用格式化字符串.請查看下面的鏈接形式:
name="Andre"
print "Hello " + name
print "Hello %s" % name
顯然與第一個語句相比,第二個print語句更加優化.第三行中的括號是不須要的。
對循環的優化所遵循的原則是儘可能減小循環過程當中的計算量,有多重循環的儘可能將內層的計算提到上一層。
能夠在循環中優化大量事件以便它們可平穩運行.下面就是可優化操做的簡短清單.在內循環中應該使用內置函數,而不是使用採用Python編寫的函 數.經過使用運行列表操做的內置函數(例如map(),reduce(),filter())代替直接循環,能夠把一些循環開銷轉移到C代碼.向 map,reduce,filter傳送內置函數更會使性能得以提升.具備多重循環之時,只有最內層循環值得優化.優化多重循環時,旨在減小內存分配的次 數.使最內層循環成爲交互做用次數最少者應該有助於性能設計.使用局部變量會大大改善循環內部的處理時間.只要可能,在進入循環前把全部全局變量和屬性搜 索複製到局部變量.若是在嵌套循環內部使用諸如range(n)之類的結構方法,則在最外層循環外部把值域分配到一個局部變量並在循環定義中使用該變量將 快速得多.
yRange=range(500) #優化1
for xItem in range(100000):
for yItem in yRange:
print xItem,yItem
這裏的另外一種優化是使用xrange做爲循環的x,由於100000項列表是一個至關大的列表.
yRange=range(500)
for xItem in xRange(100000): #優化2
for yItem in yRange:
print xItem,yItem
Python的內置函數比採用純Python語言編寫的函數執行速度要快,由於內置函數是採用C語言編寫的.map(),filter()以及 reduce就是在性能上優於採用Python編寫的函數的內置函數範例.還應瞭解,Python把函數名做爲全局常數加以處理.既然如此,前面咱們看到 的名字空間搜索的整個概念一樣適用於函數.若是能夠選擇的話,使用map()函數的隱含循環代替for循環要快得多.我在這裏提到的循環的執行時間在很大 程序上取決於傳送了什麼函數.傳送Python函數沒有傳送內置函數(諸如在operator模塊裏的那些函數)那麼快.
在Python中函數調用代價仍是很大的。在計算密集的地方,很大次數的循環體中要儘可能減小函數的調用及調用層次(能inline最好inline)。
一個良好的算法可以對性能起到關鍵做用,所以性能改進的首要點是對算法的改進。在算法的時間複雜度排序上依次是:
O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)
所以若是可以在時間複雜度上對算法進行必定的改進,對性能的提升不言而喻。
你能夠用Python寫出高效的代碼,但很難擊敗內建函數. 經查證. 他們很是快速.
你可使用 "+" 來鏈接字符串. 但因爲string在Python中是不可變的,每個"+"操做都會建立一個新的字符串並複製舊內容. 常見用法是使用Python的數組模塊單個的修改字符;當完成的時候,使用 join() 函數建立最終字符串.
>>> #This is good to glue a large number of strings
>>> for chunk in input():
>>> my_string.join(chunk)
在Python中即優雅又快速:
>>> x, y = y, x
這樣很慢:
>>> temp = x
>>> x = y
>>> y = temp
Python 檢索局部變量比檢索全局變量快. 這意味着,避免 "global" 關鍵字.
使用 "in" 關鍵字. 簡潔而快速.
>>> for key in sequence:
>>> print 「found」
將 "import" 聲明移入函數中,僅在須要的時候導入. 換句話說,若是某些模塊不需立刻使用,稍後導入他們. 例如,你沒必要在一開使就導入大量模塊而加速程序啓動. 該技術不能提升總體性能. 但它能夠幫助你更均衡的分配模塊的加載時間.
有時候在程序中你需一個無限循環.(例如一個監聽套接字的實例) 儘管 "while true" 能完成一樣的事, 但 "while 1" 是單步運算. 這招能提升你的Python性能.
Python 中條件表達式是 lazy evaluation 的,也就是說若是存在條件表達式 if x and y,在 x 爲 false 的狀況下 y 表達式的值將再也不計算。所以能夠利用該特性在必定程度上提升程序效率。
從Python 2.0 開始,你可使用 list comprehension 取代大量的 "for" 和 "while" 塊. 使用List comprehension一般更快,Python解析器能在循環中發現它是一個可預測的模式而被優化.額外好處是,list comprehension更具可讀性(函數式編程),並在大多數狀況下,它能夠節省一個額外的計數變量。列表解析要比在循環中從新構建一個新的 list 更爲高效,所以咱們能夠利用這一特性來提升運行的效率。例如,讓咱們計算1到10之間的偶數個數:
>>> # the good way to iterate a range
>>> evens = [ i for i in range(10) if i%2 == 0]
>>> [0, 2, 4, 6, 8]
>>> # the following is not so Pythonic
>>> i = 0
>>> evens = []
>>> while i < 10:
>>> if i %2 == 0: evens.append(i)
>>> i += 1
>>> [0, 2, 4, 6, 8]
生成器表達式則是在 2.4 中引入的新內容,語法和列表解析相似,可是在大數據量處理時,生成器表達式的優點較爲明顯,它並不建立一個列表,只是返回一個生成器,所以效率較高。例 如:代碼 a = [w for w in list] 修改成 a = (w for w in list),運行時間進一步減小,縮短約爲 2.98s。
大多數的Python程序員都知道且使用過列表推導(list comprehensions)。若是你對list comprehensions概念不是很熟悉——一個list comprehension就是一個更簡短、簡潔的建立一個list的方法。
>>> some_list = [1, 2, 3, 4, 5]
>>> another_list = [x + 1 for x in some_list]
>>> another_list
>>> [2, 3, 4, 5, 6]
自從python 3.1 (甚至是Python 2.7)起,咱們能夠用一樣的語法來建立集合和字典表:
>>>#Se Comprehensions
>>> some_list = [1, 2, 3, 4, 5, 2, 5, 1, 4, 8]
>>> even_set = { x for x in some_list if x % 2 == 0 }
>>> even_set set([8, 2, 4])
>>> # Dict Comprehensions
>>> d = {x: x % 2 == 0 for x in range(1, 11)}
>>> d {1: False, 2: True, 3: False, 4: True, 5: False, 6: True, 7: False, 8: True, 9: False, 10: True}
在第一個例子裏,咱們以some_list爲基礎,建立了一個具備不重複元素的集合,並且集合裏只包含偶數。而在字典表的例子裏,咱們建立了一個key是不重複的1到10之間的整數,value是布爾型,用來指示key是不是偶數。
這裏另一個值得注意的事情是集合的字面量表示法。咱們能夠簡單的用這種方法建立一個集合:
>>> my_set ={1, 2, 1, 2, 3, 4}
>>> my_set
set([1, 2, 3, 4])
而不須要使用內置函數set()。
set 的 union, intersection,difference 操做要比 list 的迭代要快。所以若是涉及到求 list 交集,並集或者差的問題能夠轉換爲 set 來操做。
set(list1) | set(list2) |
union |
包含list1和list2全部數據的新集合 |
set(list1) & set(list2) |
intersection |
包含list1和list2中共同元素的新集合 |
set(list1) - set(list2) |
difference |
在list1中出現但不在list2中出現的元素的集合 |
Python dict中使用了 hash table,所以查找操做的複雜度爲 O(1),所以對成員的查找訪問等操做字典要比 list 更快。
檢查一個元素是在dicitonary或set是否存在,這在Python中很是快的。這是由於dict和set使用哈希表來實現,查找效率能夠達 到O(1),而 list 實際是個數組,在 list 中,查找須要遍歷整個 list,其複雜度爲 O(n),所以,若是您須要常常檢查成員,使用 set 或 dict作爲你的容器。
>>> mylist = ['a', 'b', 'c'] #Slower, check membership with list:
>>> ‘c’ in mylist
>>> True
>>> myset = set(['a', 'b', 'c']) # Faster, check membership with set:
>>> ‘c’ in myset:
>>> True
這聽起來顯而易見,但常常被人忘記。對於大多數程序員來講,數一個東西是一項很常見的任務,並且在大多數狀況下並非頗有挑戰性的事情——這裏有幾種方法能更簡單的完成這種任務。
Python的collections類庫裏有個內置的dict類的子類,是專門來幹這種事情的:
>>>from collections import Counter
>>>c = Counter('hello world')
>>>c
Counter({'l': 3, 'o': 2, ' ': 1, 'e': 1, 'd': 1, 'h': 1, 'r': 1, 'w': 1})
>>>c.most_common(2)
[('l', 3), ('o', 2)]
這樣可爲你節省大量的系統內存,由於xrange()在序列中每次調用只產生一個整數元素。而相反 range(),它將直接給你一個完整的元素列表,用於循環時會有沒必要要的開銷。
這也能夠節省內存和提升性能。例如一個視頻流,你能夠一個一個字節塊的發送,而不是整個流。例如:
>>> chunk = ( 1000 * i for i in xrange(1000))
>>> chunk
>>> chunk.next()
0
>>> chunk.next()
1000
>>> chunk.next()
2000
該模塊對迭代和組合是很是有效的。讓咱們生成一個列表[1,2,3]的全部排列組合,僅需三行Python代碼:
>>> import itertools
>>> iter = itertools.permutations([1,2,3])
>>> list(iter)
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
這是一個免費的二分查找實現和快速插入有序序列的工具。也就是說,你可使用:
>>> import bisect
>>> bisect.insort(list, element)
你已將一個元素插入列表中, 而你不須要再次調用 sort() 來保持容器的排序, 由於這在長序列中這會很是昂貴.
Python中的列表實現並非以人們一般談論的計算機科學中的普通單鏈表實現的。Python中的列表是一個數組。也就是說,你能夠以常量時間 O(1) 檢索列表的某個元素,而不須要從頭開始搜索。這有什麼意義呢? Python開發人員使用列表對象insert()時, 需三思.
例如:>>> list.insert(0,item)
在列表的前面插入一個元素效率不高, 由於列表中的全部後續下標不得不改變. 然而,您可使用list.append()在列表的尾端有效添加元素. 優先選擇deque,若是你想快速的在插入或刪除時。它是快速的,由於在Python中的deque用雙鏈表實現。
原生的list.sort()函數是很是快的。 Python會按天然順序排序列表。有時,你須要非天然順序的排序。例如,你要根據服務器位置排序的IP地址。 Python支持自定義的比較,你可使用list.sort(CMP()),這會比list.sort()慢,由於增長了函數調用的開銷。若是性能有問 題,你能夠申請Guttman-Rosler Transform,基於Schwartzian Transform. 它只對實際的要用的算法有興趣,它的簡要工做原理是,你能夠變換列表,並調用Python內置list.sort() - > 更快,而無需使用list.sort(CMP() )->慢。
「@」符號是Python的裝飾語法。它不僅用於追查,鎖或日誌。你能夠裝飾一個Python函數,記住調用結果供後續使用。這種技術被稱爲memoization的。下面是一個例子:
>>> from functools import wraps
>>> def memo(f):
>>> cache = { }
>>> @wraps(f)
>>> def wrap(*arg):
>>> if arg not in cache: cache['arg'] = f(*arg)
>>> return cache['arg']
>>> return wrap
咱們也能夠對 Fibonacci 函數使用裝飾器:
>>> @memo
>>> def fib(i):
>>> if i < 2: return 1
>>> return fib(i-1) + fib(i-2)
這裏的關鍵思想是:加強函數(裝飾)函數,記住每一個已經計算的Fibonacci值;若是它們在緩存中,就不須要再計算了.
GIL是必要的,由於CPython的內存管理是非線程安全的。你不能簡單地建立多個線程,並但願Python能在多核心的機器上運行得更快。這是 由於 GIL將會防止多個原生線程同時執行Python字節碼。換句話說,GIL將序列化您的全部線程。然而,您可使用線程管理多個派生進程加速程序,這些程 序獨立的運行於你的Python代碼外。
由於GIL會序列化線程, Python中的多線程不能在多核機器和集羣中加速. 所以Python提供了multiprocessing模塊, 能夠派生額外的進程代替線程, 跳出GIL的限制. 此外, 你也能夠在外部C代碼中結合該建議, 使得程序更快.
注意, 進程的開銷一般比線程昂貴, 由於線程自動共享內存地址空間和文件描述符. 意味着, 建立進程比建立線程會花費更多, 也可能花費更多內存. 這點在你計算使用多處理器時要牢記.
好了, 如今你決定爲了性能使用本地代碼. 在標準的ctypes模塊中, 你能夠直接加載已編程的二進制庫(.dll 或 .so文件)到Python中, 無需擔憂編寫C/C++代碼或構建依賴. 例如, 咱們能夠寫個程序加載libc來生成隨機數。
然而, 綁定ctypes的開銷是非輕量級的. 你能夠認爲ctypes是一個粘合操做系庫函數或者硬件設備驅動的膠水. 有幾個如 SWIG, Cython和Boost 此類Python直接植入的庫的調用比ctypes開銷要低. Python支持面向對象特性, 如類和繼承. 正如咱們看到的例子, 咱們能夠保留常規的C++代碼, 稍後導入. 這裏的主要工做是編寫一個包裝器 (行 10~18).
Python有些模塊爲了性能使用C實現。當性能相當重要而官方文檔不足時,能夠自由探索源代碼。你能夠找到底層的數據結構和算法。
1. 若是須要交換兩個變量的值使用 a,b=b,a 而不是藉助中間變量 t=a;a=b;b=t;
>>> from timeit import Timer
>>> Timer("t=a;a=b;b=t","a=1;b=2").timeit()
0.25154118749729365
>>> Timer("a,b=b,a","a=1;b=2").timeit()
0.17156677734181258
>>>
2. 在循環的時候使用 xrange 而不是 range;使用 xrange 能夠節省大量的系統內存,由於 xrange() 在序列中每次調用只產生一個整數元素。而 range() 將直接返回完整的元素列表,用於循環時會有沒必要要的開銷。在 python3 中 xrange 再也不存在,裏面 range 提供一個能夠遍歷任意長度的範圍的 iterator。
3. 使用局部變量,避免"global" 關鍵字。python 訪問局部變量會比全局變量要快得多,所以能夠利用這一特性提高性能。
4. if done is not None 比語句 if done != None 更快,讀者能夠自行驗證;
5. 在耗時較多的循環中,能夠把函數的調用改成內聯的方式;
6. 使用級聯比較 "x < y < z" 而不是 "x < y and y < z";
7. while 1 要比 while True 更快(固然後者的可讀性更好);
8. build in 函數一般較快,add(a,b) 要優於 a+b。
這些不能替代大腦思考. 打開引擎蓋充分了解是開發者的職責,使得他們不會快速拼湊出一個垃圾設計. 以上的Python建議能夠幫助你得到好的性能. 若是速度還不夠快, Python將須要藉助外力:分析和運行外部代碼。
有益的提醒,靜態編譯的代碼仍然重要. 僅例舉幾例, Chrome,Firefox,MySQL,MS Office 和 Photoshop都是高度優化的軟件,咱們天天都在使用. Python做爲解析語言,很明顯不適合. 不能單靠Python來知足那些性能是首要指示的領域. 這就是爲何Python支持讓你接觸底層裸機基礎設施的緣由, 將更繁重的工做代理給更快的語言如C. 這高性能計算和嵌入式編程中是關鍵的功能.
調優給你的代碼增長複雜性. 集成其它語言以前, 請檢查下面的列表. 若是你的算法是"足夠好", 優化就沒那麼迫切了.
1. 你作了性能測試報告嗎?
2. 你能減小硬盤的 I/O 訪問嗎?
3. 你能減小網絡 I/O 訪問嗎?
4. 你能升級硬件嗎?
5. 你是爲其它開發者編譯庫嗎?
6. 你的第三方庫軟件是最新版嗎?
對代碼優化的前提是須要了解性能瓶頸在什麼地方,程序運行的主要時間是消耗在哪裏,對於比較複雜的代碼能夠藉助一些工具來定位,python內置了 豐富的性能分析工具,如 profile,cProfile 與 hotshot 等。其中 Profiler是python 自帶的一組程序,可以描述程序運行時候的性能,並提供各類統計幫助用戶定位程序的性能瓶頸。Python 標準模塊提供三種 profilers:cProfile,profile 以及 hotshot。
速度的問題可能很微妙, 因此不要依賴於直覺. 感謝 "cprofiles" 模塊, 經過簡單的運行你就能夠監控Python代碼.
1. 「python -m cProfile myprogram.py」
2. 使用import profile模塊
import profile
def profileTest():
Total =1;
for i in range(10):
Total=Total*(i+1)
print Total
return Total
if __name__ == "__main__":
profile.run("profileTest()")
程序的運行結果以下:
其中輸出每列的具體解釋以下:
ncalls:表示函數調用的次數;
tottime:表示指定函數的總的運行時間,除掉函數中調用子函數的運行時間;
percall:(第一個 percall)等於 tottime/ncalls;
cumtime:表示該函數及其全部子函數的調用運行的時間,即函數開始調用到返回的時間;
percall:(第二個 percall)即函數運行一次的平均時間,等於 cumtime/ncalls;
filename:lineno(function):每一個函數調用的具體信息;
若是須要將輸出以日誌的形式保存,只須要在調用的時候加入另一個參數。如 profile.run("profileTest()","testprof")。
對於大型應用程序,若是可以將性能分析的結果以圖形的方式呈現,將會很是實用和直觀,常見的可視化工具備 Gprof2Dot,visualpytune,KCacheGrind 等,讀者能夠自行查閱相關官網。
控制之後, 提供一個基本的算法性能分析. 恆定時間是理想值. 對數時間復度是穩定的. 階乘複雜度很難擴展.
O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)
Python 性能優化除了改進算法,選用合適的數據結構以外,還有幾種關鍵的技術,好比將關鍵 python 代碼部分重寫成 C 擴展模塊,或者選用在性能上更爲優化的解釋器等,這些在本文中統稱爲優化工具。python 有不少自帶的優化工具,如 Psyco,Pypy,Cython,Pyrex 等,這些優化工具各有千秋。
Psyco
psyco 是一個 just-in-time 的編譯器,它可以在不改變源代碼的狀況下提升必定的性能,Psyco 將操做編譯成有點優化的機器碼,其操做分紅三個不一樣的級別,有"運行時"、"編譯時"和"虛擬時"變量。並根據須要提升和下降變量的級別。運行時變量只是 常規 Python 解釋器處理的原始字節碼和對象結構。一旦 Psyco 將操做編譯成機器碼,那麼編譯時變量就會在機器寄存器和可直接訪問的內存位置中表示。同時 python 能高速緩存已編譯的機器碼以備從此重用,這樣能節省一點時間。但 Psyco 也有其缺點,其自己運行所佔內存較大。目前 psyco 已經不在python2.7 中支持,並且再也不提供維護和更新了,對其感興趣的能夠參考:
http://developer.51cto.com/art/201301/376509.htm
Pypy
PyPy 表示 "用 Python 實現的 Python",但實際上它是使用一個稱爲RPython的 Python 子集實現的,可以將Python 代碼轉成 C, .NET, Java 等語言和平臺的代碼。PyPy 集成了一種即時 (JIT) 編譯器。和許多編譯器,解釋器不一樣,它不關心 Python代碼的詞法分析和語法樹。 由於它是用 Python 語言寫的,因此它直接利用 Python語言的 Code Object。Code Object 是 Python 字節碼的表示,也就是說, PyPy直接分析 Python 代碼所對應的字節碼,這些字節碼即不是以字符形式也不是以某種二進制格式保存在文件中, 而在 Python 運行環境中。目前版本是 1.8. 支持不一樣的平臺安裝,windows 上安裝 Pypy 須要先下載https://bitbucket.org/pypy/pypy/downloads/pypy-1.8-win32.zip, 而後解壓到相關的目錄,並將解壓後的路徑添加到環境變量 path 中便可。在命令行運行 pypy,若是出現以下錯誤:"沒有找到 MSVCR100.dll, 所以這個應用程序未能啓動,從新安裝應用程序可能會修復此問題",則還須要在微軟的官網上下載 VS 2010 runtime libraries 解決該問題。具體地址爲 http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=5555
Cython
Cython 是用 python 實現的一種語言,能夠用來寫 python 擴展,用它寫出來的庫均可以經過 import 來載入,性能上比 python 的快。cython 裏能夠載入 python 擴展 ( 好比 import math),也能夠載入 c 的庫的頭文件 ( 好比:cdef extern from "math.h"),另外也能夠用它來寫 python 代碼。將關鍵部分重寫成 C 擴展模塊。
以上第三方工具可參考:
http://www.linuxidc.com/Linux/2012-07/66757p3.htm
但願這些Python建議能讓你成爲一個更好的開發者. 最後, 我須要指出, 追求性能極限是一個有趣的遊戲, 而過分優化就會變成嘲弄了. 雖然Python授予你與C接口無縫集成的能力, 你必須問本身你花數小時的艱辛優化工做用戶是否買賬. 另外一方面, 犧牲代碼的可維護性換取幾毫秒的提高是否值得. 團隊中的成員經常會感謝你編寫了簡潔的代碼. 儘可能貼近Python的方式, 由於人生苦短. :)