文中如對專業術語的翻譯有誤,請Python高手指正(Pythonistas),謝謝!另外,原文中的Pythonic一詞,大意指符合Python語言規範、特性和數據結構的編碼方式,蘊涵較爲豐富,爲了行文更順暢、容易理解,此文暫時用簡潔、優雅代替。-- <cite>EarlGrey@編程派</cite>html
原文連接:Trey Hunner,發佈於11月9日。
譯文連接:編程派node
有時候,利用Python語言簡潔、優雅地解決問題的方法,會隨着時間變化。隨着Python不斷進化,統計列表元素數量的方法也在改變。python
以計算元素在列表中出現的次數爲例,咱們能夠編寫出許多不一樣的實現方法。在分析這些方法時,咱們先不關注性能,只考慮代碼風格。git
要理解這些不一樣的實現方式,咱們得先知道一些歷史背景。幸運的是,咱們生活在"__future__"世界,擁有一臺時間機器。接下來,咱們一塊兒坐上時光機,回到1997年吧。github
if
語句1997年1月1日,咱們使用的是Python 1.4。如今有一個不一樣顏色組成的列表,咱們想知道列表裏每種顏色出現的次數。咱們用字典來計算吧!express
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = {} for c in colors: if color_counts.has_key(c): color_counts[c] = color_counts[c] + 1 else: color_counts[c] = 1
注意:咱們沒有使用+=
,由於增量賦值直到Python 2.0纔出現;另外,咱們也沒有使用c in color_counts
這個慣用法(idiom),由於這也是Python 2.2中才發明的,編程
運行上述代碼以後,咱們會發現color_counts
字典裏,如今包含了列表中每種顏色的出現次數。數據結構
color_counts {'brown': 3, 'yellow': 2, 'green': 1, 'black': 1, 'red': 1}
上面的實現很簡單。咱們遍歷了每一種顏色,並判斷該顏色是否在字典中。若是不在,就在字典加入該顏色;若是在,就增長這種顏色的計數。less
咱們還能夠把上面的代碼改寫爲:ide
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = {} for c in colors: if not color_counts.has_key(c): color_counts[c] = 0 color_counts[c] = color_counts[c] + 1
若是列表稀疏度高(即列表中不重複的顏色數量不少),這段代碼可能運行的會有點慢。由於咱們如今要執行兩個語句,而不是一個。可是咱們不關心性能問題,咱們只關注編碼風格。通過思考,咱們決定採用新版的代碼。
try
代碼塊(Code Block)1997年1月2日,咱們使用的仍是Python 1.4。今早醒來的時候,咱們忽然意識到:咱們的代碼遵循的是「三思然後行」(Look Before You Leap,即事先檢查每一種可能出現的狀況)原則,但實際上咱們應該按照「得到諒解比得到許可容易」(Easier to Ask Forgiveness, Than Permission,即不檢查,出了問題由異常處理來處理)的原則進行編程,由於後者更加簡潔、優雅。咱們用try-except
代碼塊來重構下代碼吧:
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = {} for c in colors: try: color_counts[c] = color_counts[c] + 1 except KeyError: color_counts[c] = 1
如今,咱們的代碼嘗試增長每種顏色的計數。若是某顏色不在字典裏,那麼就會拋出KeyError,咱們隨之將該顏色的計數設置爲1。
get
方法1998年1月1日,咱們已經升級到了Python 1.5。咱們決定重構以前的代碼,使用字典中新增的get
方法。
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = {} for c in colors: color_counts[c] = color_counts.get(c, 0) + 1
如今,咱們的代碼會遍歷每種顏色,從字典中獲取該顏色的當前計數值。若是沒有這個計數值,則該顏色的計數值默認爲0,而後在數值的基礎上加1。最後將字典中相應鍵的值設置爲新的計數。
把主要代碼都寫在一行裏,感受很酷,可是咱們不敢徹底確定這種作法更加簡潔、優雅。咱們以爲可能有點太聰敏了,因此仍是撤銷了此次的重構。
setdefault
方法2001年1月1日,咱們如今使用的是Python 2.0。咱們據說字典類型如今有一個setdefault
方法,決定利用它重構咱們的代碼。咱們還決定使用新增長的+=
增量賦值運算符。
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = {} for c in colors: color_counts.setdefault(c, 0) color_counts[c] += 1
不管是否須要,咱們在每一次循環時都會調用setdefault
方法。但這樣作,的確會讓代碼看上去可讀性更高。咱們發現這種方法比以前的代碼更加簡潔、優雅,因此提交了這次修改。
fromkeys
方法2004年1月1日,咱們使用的是Python 2.3。咱們據說字典新增了一個叫fromkeys
的類方法(class method),能夠利用列表中的元素做爲鍵來構建字典。咱們使用新方法重構了代碼:
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = dict.fromkeys(colors, 0) for c in colors: color_counts[c] += 1
這段代碼將不一樣的顏色做爲鍵,建立了一個新的字典,每一個鍵的值被默認設置爲0。這樣,咱們增長每一個鍵的值時,就不用擔憂是否已經進行了設置。咱們也不須要在代碼中進行檢查或異常處理了,這看上去是個改進。咱們決定就這樣修改代碼。
2005年1月1日,咱們如今用的是Python 2.4。咱們發現能夠利用集合(Python 2.3中發佈,2.4版成爲內置類型)與列表推導式(Python 2.0中發佈)來解決計數問題。進一步思考以後,我想起來Python 2.4中還發布了生成器表達式(generator expressions),咱們最後決定不用列表推導式,而是採用生成器表達式。
colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = dict((c, colors.count(c)) for c in set(colors))
注意:咱們這裏使用的不是字典推導式,由於字典推導式直到Python 2.7才被髮明。
運行成功了,並且只有一行代碼。可是這種代碼夠簡潔、優雅嗎 ?
咱們想起了Python之禪(Zen of Python),這個Python編程指導原則起源於一個Python郵件列表,並悄悄地收進了Python 2.2.1版本中。咱們在REPL(read-eval-print loop,交互式解釋器)界面中輸入import this
:
import this The Zen of Python, by Tim Peters Beautiful is better than ugly. # 優美賽過醜陋 Explicit is better than implicit. # 明確賽過含蓄 Simple is better than complex. # 簡單賽過複雜 Complex is better than complicated. # 複雜賽過難懂 Flat is better than nested. # 扁平賽過嵌套 Sparse is better than dense. # 稀疏賽過密集 Readability counts. # 易讀亦有價 Special cases aren't special enough to break the rules. # 特例也不能特殊到打破規則 Although practicality beats purity. # 儘管實用會擊敗純潔 Errors should never pass silently. # 錯誤永遠不該默默地溜掉 Unless explicitly silenced. # 除非明確地使其沉默 In the face of ambiguity, refuse the temptation to guess. # 面對着不肯定,要拒絕猜想的誘惑 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. # 也許這個方法開始不是很明顯,除非你是荷蘭人 Now is better than never. #如今作也要賽過不去作 Although never is often better than right now. # 儘管不作一般好過馬上作 If the implementation is hard to explain, it's a bad idea. # 若是實現很難解釋,那它就是一個壞想法 If the implementation is easy to explain, it may be a good idea. # 若是實現容易解釋,那它可能就是一個好想法 Namespaces are one honking great idea -- let's do more of those! # 命名空間是一個響亮的出色想法–就讓咱們多用用它們
譯者注:Python之禪的翻譯版本不少,這裏選用的譯文出自啄木鳥社區。
咱們的代碼變得更復雜,時間複雜度從O(n)增長到了O(n2);還變的更醜,可讀性更差了。咱們那樣改,是一次有趣的嘗試,可是一行代碼的實現形式,沒有咱們以前的方法簡潔、優雅。咱們最後仍是決定撤銷修改。
defaultdict
方法2007年1月1日,咱們使用的是Python 2.5。咱們剛發現,defaultdict
已經被加入標準庫。這樣,咱們就能夠把字典的默認鍵值設置爲0了。讓咱們使用defaultdict
重構代碼:
from collections import defaultdict colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = defaultdict(int) for c in colors: color_counts[c] += 1
那個for
循環如今變得真簡單!這樣確定是更加簡潔、優雅了。
咱們發現,color_counts
這個變量的行爲如今有點不一樣,可是它的確繼承了字典的特性,支持全部相同的映射功能。
color_counts defaultdict(<type 'int'>, {'brown': 3, 'yellow': 2, 'green': 1, 'black': 1, 'red': 1})
咱們在這裏沒有把color_counts
轉換成字典,而是假設其餘的代碼也使用鴨子類型(duck typing, Python中動態類型的一種,這裏的意思是:其餘代碼會將color_counts
視做字典類型),再也不改動這個相似字典的對象。
Counter
類2011年1月1日,咱們使用的是Python 2.7。別人告訴咱們,以前使用defaultdict
編寫的代碼,再也不是統計顏色出現次數最簡潔、優雅的方法了。Python 2.7中新引入了一個Counter
類,能夠徹底解決咱們的問題。
from collections import Counter colors = ["brown", "red", "green", "yellow", "yellow", "brown", "brown", "black"] color_counts = Counter(colors)
還有比這更簡單的方法嗎?這個必定是最簡潔、優雅的實現了。
與defaultdict
同樣,Counter
類返回的也是一個相似字典的對象(實際是字典的一個子類)。這對知足咱們的需求來講足夠了,因此咱們就這麼幹了。
color_counts Counter({'brown': 3, 'yellow': 2, 'green': 1, 'black': 1, 'red': 1})
請注意,在編寫這些實現方式時,咱們都沒有關注效率問題。大部分方法的時間複雜度相同(O(n)),可是不一樣的Python語言實現形式(如CPython, PyPy,或者Jython)下,運行時間會有差別。
儘管性能不是咱們的主要關注點,我仍是在CPython 3.5.0的實現下測試了運行時間。從中,你會發現一個有趣的現象:隨着列表中顏色元素的密度(即相同元素的數量)變化,每一種實現方法的相對效率也會不一樣。
根據Python之禪,「 應該有一個——寧可只有一個明顯的實現方法」。這句話所說的狀態值得追求,但事實是,並不老是隻有一種明顯的方法。這個「明顯」的方法會隨着時間、需求和專業水平,不斷地變化。
「簡潔、優雅」(即Pythonic)也是一個相對的概念。