(全文字數: 3000, 閱讀時間: 5分鐘)
Python是我的日常程式語言,公眾號有部分文章會寫我的Python使用心得。去年3月份寫過一篇關於Python lambda的技術文章,雖然在公眾號上閱讀量平平,但是轉載到CSDN博客之後卻火了。累計閱讀量已經接近15萬,並且每天都在持續增加。許多同學也是看了這篇文章才關注我的公眾號或加我好友的。
在文章中,我對Python匿名函數lambda作了"一個語法,三個特性,四個用法"的總結。儘管網絡上關於Python lambda的資料汗牛充棟,但是像那篇文章一樣把它講得透徹和易懂的卻不多。感興趣的同學可以點開看看。在文章末尾,我留下了一個伏筆,提到了在Python社區中,對於是否應該使用lambda是存在爭議的。贊同者有之,反對者亦有之。
一年多以前,我還是lambda的重度使用者,並且在網絡上搖旗吶喊,寫文章幫助大家入門lambda。而現在,隨著經驗的積累和認知的轉變,我想說的是,自己已經在代碼中慢慢放棄使用lambda了。今天,我就給大家講講,為什麼會發生這種180度大轉彎的事情。
我們知道,Python基於lambda的編程具有代碼緊湊和簡潔的優勢。然而,它的不足也是明顯的,那就是可讀性較差。這是因為,lambda定義的是一個沒有名字的函數,因此我們可能無法很容易知道它是幹什麼的。並且,很多時候lambda函數是作為參數傳給另外一個高階函數的,這意味著在一行代碼中會同時出現函數的定義和函數的調用,從而更增加了理解難度。
對於編程來說,代碼編寫的頻率是遠遠小於代碼被閱讀的頻率的。因此代碼的可讀性成為一件至關重要的事情。lambda的弱可讀性,是它的一個硬傷。
接下來,大家會看到,有兩類情況,我們可以放棄lambda,提升代碼可讀性。先說第一類情況:我們可能找到更好的替代品,在保持代碼簡潔性不變的同時,提升代碼的可讀性。下面是一些例子。
lambda的一種用法是與filter函數結合,過濾列表中符合條件的元素。例如將列表[1, 2, 3]中是3的倍數的數字過濾出來的lambda實現為:
filter(lambda x: x % 3 == 0, [1, 2, 3])這時,我們可以用list comprehension(列表理解)來完成同樣的工作:
[i for i in [1, 2, 3] if i % 3 == 0]大家可以看到,代碼簡潔性並沒有變化,而可讀性卻明顯提升了。
類似的,lambda常常與map函數結合,針對列表的每一個元素做同樣的某種操作。這種場景也可以用列表理解來替代,以提升可讀性。例如:
map(lambda x: x+1, [1, 2, 3])將列表[1, 2, 3]中的元素分別加1,其結果[2, 3, 4]。可以被替換為:
[i + 1 for i in [1, 2, 3]]可見,在一些場景下,使用列表理解能夠替換lambda,在保持代碼簡潔性的同時,提升代碼可讀性,可謂"一石二鳥"。當然,這些情況下我們也可以用生成器表達式(generator expression)來替換lambda,效果與列表理解一致,並且程序性能更優。為了節省篇幅我們這裡不展開,感興趣的同學可以去網上了解下。
繼續舉例。lambda經常與sorted函數結合,針對列表進行排序。某些時候我們也可以放棄lambda。例如,
sorted([1, 3, -2, -4], key=lambda x: abs(x))針對列表[1, 3, -2, -4],根據元素絕對值大小進行排序,輸出結果是[1, -2, 3, -4]。由於abs是Python內置函數,因此lambda是多此一舉的。類似這種可以直接使用Python內置函數的情況,建議捨棄lambda:
sorted([1, 3, -2, -4], key=abs)類似的,注意到Python標準庫operator提供了用函數來調用Python操作符的方法。因此,在能夠使用operator提供的操作符函數的地方,也可以捨棄lambda。
例如,下面這個lambda和reduce函數結合使用的例子。獲得列表[2, 3, 4]所有元素的乘積,結果是24:
reduce(lambda x, y: x * y, [2, 3, 4], 1)基於operator提供的mul函數(等價於*操作符)的替代方案是:
from operator import mulreduce(mul, [2, 3, 4], 1)operator提供的不只有操作符函數。例如attrgetter函數能夠得到對象的屬性,可以在下面這種情況下替換掉lambda:
sorted(nodes, key=lambda p: p["width"])from operator import attrgettersorted(nodes, key=attrgetter("width"))以上介紹了如何使用列表理解/生成器表達式/Python內置函數/Python標準庫operator來替換lambda。它們都是不錯的替代品,能夠在保證簡潔性的情況下提升代碼的可讀性。
這是第一類情況。現在看第二類情況,那就是在第一類情況不適用的時候,我們如有必要,還可以犧牲簡潔性來換取代碼可讀性的提升。
例如,下面這個lambda與sorted函數結合的例子:
points = [((1, 2), 'red'), ((3, 4), 'green')]points_by_color = sorted(points, key=lambda p: p[1])這裡排序的對象是一個元祖列表,排序依據是元祖的索引為1的元素。這段代碼不僅存在hardcode,而且可讀性也差,排序的依據並不清晰。
這時候,我們可以使用def定義一個排序依據函數color_of_point,將其作為參數傳遞給sorted函數。在函數名字和函數文檔字符串(docstring)的輔助下,代碼容易理解多了:
def color_of_point(point):"""Return the color of the given point.""" (x, y), color = point return color
points = [((1, 2), 'red'), ((3, 4), 'green')]points_by_color = sorted(points, key=color_of_point)從這個例子可以看到,當lambda函數的含義不直白或者存在hardcode時,可以放棄lambda而定義一個正式的函數。雖然需要多寫幾行代碼,但是可讀性得到提升,投入產出比是好的。
以上就是我總結的關於放棄lambda的兩點建議。一是使用列表理解/生成器表達式/Python內置函數/Python標準庫operator來替換lambda,在保證簡潔性的情況下,提升代碼可讀性。二是定義一個正式的函數,將函數的功能描述清楚,犧牲程序簡潔性,換得可讀性的提升。
這兩點建議應該能夠覆蓋大部分實際情況。在我看來,只有這兩點建議都行不通時,才保留使用lambda。當然,我不認為徹底放棄lambda在現階段是合理的。例如在下面這個依據元素與數字2的距離的大小進行排序的例子中,lambda的價值仍然存在:
sorted([1, 3, -2, -4], key=lambda x: abs(x-2))最後,歡迎大家留言發表自己對Python lambda的看法。
推薦閱讀:
可能是史上最全Python lambda講解
Python: 告別Print?