摘要:譯文鏈接編程派有時候,利用語言簡潔優雅地解決問題的方法,會隨著時間變化。隨著不斷進化,統計列表元素數量的方法也在改變。最后將字典中相應鍵的值設置為新的計數。我們發現這種方法比之前的代碼更加簡潔優雅,所以提交了此次修改。
文中如對專業術語的翻譯有誤,請Python高手指正(Pythonistas),謝謝!另外,原文中的Pythonic一詞,大意指符合Python語言規范、特性和數據結構的編碼方式,蘊涵較為豐富,為了行文更順暢、容易理解,此文暫時用簡潔、優雅代替。-- EarlGrey@編程派
原文鏈接:Trey Hunner,發布于11月9日。
譯文鏈接:編程派
有時候,利用Python語言簡潔、優雅地解決問題的方法,會隨著時間變化。隨著Python不斷進化,統計列表元素數量的方法也在改變。
以計算元素在列表中出現的次數為例,我們可以編寫出許多不同的實現方法。在分析這些方法時,我們先不關注性能,只考慮代碼風格。
要理解這些不同的實現方式,我們得先知道一些歷史背景。幸運的是,我們生活在"__future__"世界,擁有一臺時間機器。接下來,我們一起坐上時光機,回到1997年吧。
if 語句1997年1月1日,我們使用的是Python 1.4。現在有一個不同顏色組成的列表,我們想知道列表里每種顏色出現的次數。我們用字典來計算吧!
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}
上面的實現很簡單。我們遍歷了每一種顏色,并判斷該顏色是否在字典中。如果不在,就在字典加入該顏色;如果在,就增加這種顏色的計數。
我們還可以把上面的代碼改寫為:
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。這樣,我們增加每個鍵的值時,就不用擔心是否已經進行了設置。我們也不需要在代碼中進行檢查或異常處理了,這看上去是個改進。我們決定就這樣修改代碼。
推導式(Comprehension)與集合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(, {"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)也是一個相對的概念。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/44193.html
摘要:整理可愛的簡化元編程的是什么鬼,多線程性能究竟如何淺談的語句基礎深入理解中的賦值引用拷貝作用域命名空間和作用域窺探引用計數編寫漂亮的,可讀的代碼的最佳時間語言中計數方法的演變描述符解密的內存管理機制深入的內存管理源碼剖析 Python 整理 Python3 Official Documentation Python3 Document Coding Style PEP 8 Encodi...
摘要:作者宋天龍來源科技大本營導語一切都始于年的那個圣誕節,的誕生并不算恰逢其時,它崛起充滿了機遇巧合,也有其必然性。年的圣誕節,開始編寫語言的編譯器。年發布的標志著的框架基本確定。年月發布了系列的最后一個版本,主版本號為。 showImg(https://segmentfault.com/img/remote/1460000019862276); 作者 | 宋天龍來源 | AI科技大本營 ...
摘要:內部通過引用計數機制來統計一個對象被引用的次數。下一步,就該被我們的垃圾回收器給收走了。而我們垃圾回收機制只有當引用計數為的時候才會釋放對象。以空間換時間的方法提高垃圾回收效率。 人生苦短,只談風月,談什么垃圾回收。據說上圖是某語言的垃圾回收機制。。。我們寫過C語言、C++的朋友都知道,我們的C語言是沒有垃圾回...
摘要:代碼易于閱讀,并且,易于維護這一條是看來的,這可能只是一種感覺問題。因此的垃圾回收機制由一個引用計數器和一個循環垃圾收集器組成。類的繼承支持多重繼承父類的構造函數必須在子類中親自調用 Python 初探 Python是。。? python是一種解釋型腳本語言。 python語言是一句一句執行的,可以再命令提示符中直接編寫程序并隨時獲得結果 python支持面向對象 我已經會c了,除...
閱讀 3194·2021-11-10 11:35
閱讀 1300·2019-08-30 13:20
閱讀 1123·2019-08-29 16:18
閱讀 2135·2019-08-26 13:54
閱讀 2162·2019-08-26 13:50
閱讀 960·2019-08-26 13:39
閱讀 2477·2019-08-26 12:08
閱讀 1952·2019-08-26 10:37