尋找符合Python風(fēng)格的求和方式

本文摘自《流暢的Python》(《FluentPython》),作者Luciano Ramalho,譯者安道 吳珂


=============

序言

就像“什么是美”沒有確切的答案一樣,“什么是Python風(fēng)格”也沒有標(biāo)準(zhǔn)答案。如果回答“地道的Python”,不能讓人100%滿意,因?yàn)閷?duì)你來說是“地道的”,在我看來卻可能不是。但我可以肯定的是,“地道”并不是指使用最鮮為人知的語言特性。

先決條件

  • 歸約函數(shù)(reduce、sum、any、all)將序列或有限的迭代對(duì)象變成一個(gè)聚合結(jié)果
  • reduce函數(shù)在目前的更新中已經(jīng)被收進(jìn)functools包中了
  • 函數(shù)簽名reduce(function, iterable, initializer)
    其中第三個(gè)參數(shù)initializer是可選的。它避免了空序列拋出異常,如果令了這個(gè)參數(shù),將從這個(gè)參數(shù)開始。(一般為恒等值,如+|^等令為1,+ &令為0)。
    function是一個(gè)接收兩個(gè)參數(shù)的函數(shù)。
  • 調(diào)用的一般過程為:如序列[1,2,3,4,5]
    fun(1,2) (=a)
    fun(a, 3) (=b)
    fun(b, 4) etc...

問題提出

Python-list上有一篇題為“Pythonic Way to Sum n-th List Element?”(鏈接似已失效?)的話題與本篇討論的reduce函數(shù)有關(guān)。
該話題發(fā)起人Guy Middleton說他不喜歡使用lambda表達(dá)式,問如下方案可否改進(jìn):

>>>my_list = [[1, 2, 3], [40, 50, 60], [9, 8, 7]]
>>>import functools
>>>functools.reduce(lambda a,, b: a+b, [sub[1] for sub in my_list])
60

這段代碼包含了:lambda、reduce和列表推導(dǎo)。這對(duì)于討厭lambda和看不上列表推導(dǎo)的人兩邊不討好——這兩種人都很多。如果使用lambda,或許就不應(yīng)該使用列表推導(dǎo)——過濾除外,但這不是過濾((for x in list if x > 0) 其中if就是過濾表達(dá)式)。


=============

本書的作者給出的方案是:

>>> functools.reduce(lambda a, b: a + b[1], my_list, 0)
60

但作者表示不會(huì)在真實(shí)代碼如此寫,(因?yàn)樗膊?strong>喜歡lambda表達(dá)式)。此處僅僅是為了舉例說明不使用列表推導(dǎo)怎么做。


=============

第一個(gè)答案

,來自Fernando Perez,IPython的創(chuàng)建者,強(qiáng)調(diào)了NumPy支持n維數(shù)組和n維切片:

>>> import numpy as np
>>>my_array = np.array(my_list)
>>>np.sum(my_array[:, 1]
60

(即一維全體,二維的下標(biāo)1元素)


=============

第二個(gè)答案

,Guy Middleton推崇Paul Rubin和Skip Montanaro給出的下述方案:

>>> import operator
>>> functools.reduce(operator.add, my_list, 0)
60

其中,operator庫中包含諸如add, xor等常用數(shù)字運(yùn)算函數(shù),包含兩個(gè)參數(shù)


=============

以及

,EvanSimpson問道:"這樣做有什么錯(cuò)?"

>>> total = 0
>>> for sub in my_list:
...            total += sub[1]
>>>total
60

foreach循環(huán)。
許多人都覺得這也很符合Python風(fēng)格。Alex Martelli甚至說,Guido或許就會(huì)這么做。
作者喜歡這段代碼,更喜歡David Eppstein對(duì)此給出的評(píng)論:

如果你想計(jì)算列表各個(gè)元素的和,寫出的代碼應(yīng)該看起來像是在“計(jì)算元素之和”,而不是“迭代元素,維護(hù)一個(gè)變量t,再執(zhí)行一系列求和操作”。如果不能站在一定高度上表明意圖,讓語言去關(guān)注低層次操作,那么要高級(jí)語言干嘛?

之后Alex Martelli又建議:

求和操作經(jīng)常需要,我不介意Python提供一個(gè)這樣的內(nèi)置函數(shù)。但是,在我看來,“reduce(operator..add,...”不是好方法(作為一名APL老程序員和FP語言的愛好者,我應(yīng)該喜歡,但我并不喜歡)。


隨后

Alex建議提供并實(shí)現(xiàn)了sum()函數(shù)并在之后的Python2.3中內(nèi)置了。因此,Alex喜歡的句法變成了標(biāo)準(zhǔn):(列表推導(dǎo),僅能生成list)

>>> sum([sub[1] for sub in my_list])
60

下一年年末(2004年11月),Python2.4發(fā)布,這一版引入了生成器表達(dá)式。因此,作者建議,當(dāng)前這個(gè)問題最符合Python風(fēng)格的答案是:(生成任何類型的序列)

>>> sum(sub[1] for sub in my_list)
60

這樣寫不僅比reduce函數(shù)可讀性更強(qiáng),而且還避免了空序列導(dǎo)致的陷阱:sum([])的結(jié)果是0,就這么簡單
在這次討論中,Alex Martelli指出,Python2內(nèi)置的reduce函數(shù)成事不足敗事有余,因?yàn)樗扑]的地道編程方式難以理解。他的觀點(diǎn)最優(yōu)說服力:Python3把reduce函數(shù)移到functools模塊中了。

end

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容