本文摘自《流暢的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模塊中了。