當前位置:
首頁 > 知識 > 不要在Python中編寫 lambda 表達式了

不要在Python中編寫 lambda 表達式了

不要在Python中編寫 lambda 表達式了

在不討論 lambda 表達式的情況下, 我很難深入地講授 Python 類. 我經常遇到有關它們的問題. 學生們往往會在 StackOverflow 或者他們同事的代碼中(實際上, 這個也可能來自StackOverflow)碰到他們.

我對 lambda 有很多的疑問, 我很猶豫是否要推薦學生接受 Python lambda 表達式. 多年來我一直都很厭惡 lambda 表達式, 自從幾年前我開始頻繁教授 Python 後, 我對它的厭惡與日俱增.

我將會說明我對 lambda 表達式的看法, 以及為何我傾向於建議學生們避免使用它.

Python 中的 lambda 表達式: 它們是什麼?

lambda 表達式是 Python 中創建匿名函數的一個特殊語法. 我稱 lambda語法本身為lambda 表達式, 而它返回的函數我稱之為lambda 函數.

Python 的 lambda 表達式允許在一行代碼中創建一個函數並傳遞(通常傳遞到另外一個函數).

lambda 表達式允許我們使用此代碼:

不要在Python中編寫 lambda 表達式了

並將其轉換為以下代碼:

不要在Python中編寫 lambda 表達式了

lambda 表達式僅僅是創建函數的一個特殊方法. 它們只包含一條語句, 並自動返回這條語句的結果.

lambda 表達式本身的局限性實際上是其吸引力的一部分. 當經驗豐富的 Python 程序員看到一個lambda 表達式時, 他們知道他們正在使用一個僅在一個地方有效的函數, 並且只做一件事情.

如果你曾經在 JavaScript 中使用過匿名函數, 那麼Python 中的 lambda 表達式與之相同, 除了具有更多限制以及與傳統函數完全不同的語法.

通常使用的地方

你通常會在調用接受函數作為參數的函數(或類)時, 使用 lambda表達式.

Python 內置的 sorted函數接受一個函數作為它的key參數. 這個 key 函數用於在決定條目排序順序時計算比較鍵的值.

所以 sorted可以作為一個經常使用 lambda 表達式範例:

不要在Python中編寫 lambda 表達式了

上述代碼返回了對給定顏色以不區分大小寫方式排序的結果.

sorted函數並不是 lambda 表達式的唯一用法, 但卻是最普遍的一個.

lambda 的利弊

圍繞 lambda 表達式和 def定義的函數之間的一系列對比, 我發表一下看法. 這兩類工具都可以提供函數, 但它們都有各自的限制, 使用了不同的語法.

lambda 表達式與 def的主要不同點:

  1. 可以立刻傳遞(無需變數)

  2. 在內部只能包含一行代碼

  3. 自動返回結果

  4. 既沒有文檔字元串, 也沒有名稱

  5. 使用了不同且不常見的語法

事實上, lambda 表達式能夠被傳遞是它們最大的優勢. 自動返回結果很簡潔, 但在我看來並不是很大的優勢. "單行代碼"的限制總體上不好不壞. 而 lambda 函數沒有文檔字元串和名稱令人遺憾, 而它們的一些不常見的語法可能會對新的Pythonista造成困擾.

總的來說, 我覺得 lambda 表達式的缺點略微超過了它的優點, 但我對它們最大的怨念是它們往往被濫用或者過度使用.

lambda 被濫用和過度使用

當我在陌生代碼中看到 lambda 表達式時, 我立刻會新生疑慮. 當我在自然環境下遇到 lambda 表達式時, 我經常發現去掉它們之後能提高代碼的穩定性.

有時候 lambda 表達式會被濫用, 意味著它們的使用方式通常不理想. 另外有時候 lambda 表達式僅僅被過度使用, 意味著它們可以被接受, 但我個人更願意看到以其他不同方式編寫的代碼.

讓我們來看一下 lambda 表達式被濫用和過度使用的幾種方式.

濫用: 命名 lambda 表達式

官方 Python 風格指南 PEP8 建議永遠不要編寫這樣的代碼:

不要在Python中編寫 lambda 表達式了

上述語句創建了一個匿名函數並賦值到一個變數. 上面的代碼忽視了用 lambda 的原因: lambda 函數可以被直接傳遞而無需先賦值給一個變數.

如果你想創建一個一行代碼的函數並存儲到變數中, 你應該使用def:

不要在Python中編寫 lambda 表達式了

PEP8 推薦這種方式, 因為命名函數是一個常見並容易理解的東西. 同時給函數一個合適的名稱也是很有好處的, 可以讓調試簡單一些. 而與 def定義的函數不同, lambda 函數從來都沒有一個名稱(名稱都是<lambda>):

不要在Python中編寫 lambda 表達式了

如果你想創建一個函數並存儲到變數中, 請使用 def來定義. 這正是它的用途. 無論你的函數是一行代碼還是在另外一個函數中定義, 都可以,def正適合這些應用場景.

濫用: 調用不必要的函數

我經常看到用 lambda 表達式封裝一個已經很適合當前問題的函數.

例如這段代碼:

不要在Python中編寫 lambda 表達式了

寫這段代碼的人很可能了解過, 知道 lambda 表達式是用來創建一個可傳遞函數的. 但他們卻忽略了一個更大一些的理念: Python 中所有的函數(不止 lambda 函數)都是可傳遞的.

既然 abs(返回一個數字的絕對值) 是一個函數並且所有函數都是可傳遞的, 實際上我們可以將上述代碼編寫為:

不要在Python中編寫 lambda 表達式了

這個例子可能會讓人感到有些假, 但以這種方式來使用 lambda 表達式並不十分罕見. 這是我看到的另外一個例子:

不要在Python中編寫 lambda 表達式了

因為我們接受與我們傳給 min完全相同的參數, 所以完全沒有必要調用額外的函數. 我們可以直接將min函數傳遞給key:

不要在Python中編寫 lambda 表達式了

如果你已經有了另一個符合你要求的函數, 則不需要一個 lambda 函數.

過度使用: 簡單, 但不常用的函數

lambda 表達式通常用於創建一個在元組中返回一系列值的函數.

不要在Python中編寫 lambda 表達式了

這裡的 key 所傳的函數讓我們可以根據長度以及大小寫標準化的名稱來對顏色進行排序.

下面的代碼與上面的功能相同, 但我認為更有可讀性:

不要在Python中編寫 lambda 表達式了

代碼看上去有點啰嗦, 但我覺得 key 函數的名稱可以讓排序的依據更加清晰. 我們不是只依據長度排序, 也不是只依據顏色排序: 我們同時使用了兩者.

如果一個函數很重要, 那麼它應當有一個名稱. 你可以爭論說, lambda 表達式中使用的大多數函數都不重要, 不值得給一個名稱, 但命名函數通常沒什麼缺點, 而且我發現它通常會使我的代碼整體上可讀性更好.

給函數命名通常會讓代碼更有可讀性, 同樣的, 使用元組拆包來命名變數而不是使用隨機索引查找的方式通常會讓代碼更有可讀性.

過度使用: 多行代碼有幫助的時候

有時候 lambda 表達式"只有一行"這方面的特性會導致我們用複雜的方式來編寫代碼. 例如下面的例子:

不要在Python中編寫 lambda 表達式了

在這裡我們對索引查找做了硬編碼以按照顏色來對點進行排序. 如果我們使用一個命名函數, 我們可以用元組拆包來讓代碼更有可讀性:

不要在Python中編寫 lambda 表達式了

比起使用硬編碼索引查找,元組拆包可以提升可讀性. 使用 lambda 表達式通常意味著犧牲掉一些 Python 語言的特性, 尤其是需要多行代碼的時候(比如額外的賦值語句).

過度使用: lambda 與 map 和 filter

Python 的 map 和 filter 函數經常與 lambda 表達式搭配在一起使用. 當在 StackOverflow 上提問"什麼是 lambda 表達式"的問題時, 經常會看到以下例子中的代碼:

不要在Python中編寫 lambda 表達式了

我發現這些例子有點令人困惑, 因為我幾乎從未在我的代碼中使用 map 和 filter.

Python 的 mapfilter函數用來循環並創建一個新的可迭代對象, 循環期間對每個元素做一些細微修改或者根據匹配的條件過濾到只剩一些元素. 我們完全可以只使用列表推導和生成器表達式來完成這兩項任務:

不要在Python中編寫 lambda 表達式了

就個人而言, 我更願意看到上面的生成器表達式用多行代碼來寫(可以參見我關於推導的文章), 但我發現即使是這些單行的生成器表達式也比調用 mapfilter更具有可讀性.

mapping 和 filtering 的一般操作是很有用的, 但我們實際上並不需要 mapfilter函數本身. 生成器表達式是一種特殊的語法, 僅適用於 mapping 和 filtering 任務. 所以我的建議是使用生成器表達式來替代mapfilter函數.

濫用: 有時你甚至不需要去傳遞一個函數

那需要傳遞並執行單個操作函數的情況該怎麼辦?

熱衷於函數式編程的新 Pythonistas 有時會寫如下的代碼:

不要在Python中編寫 lambda 表達式了

上述代碼對 numbers列表中的所有數字做了加法. 但還有一個更好的方式來做這個操作:

不要在Python中編寫 lambda 表達式了

Python 內置的 sum函數就是專門做這個任務的.

sum函數以及其他的一些專門的 Python 工具很容易被忽視. 但我建議你在需要時尋找更專業的工具, 因為它們通常會讓代碼更有可讀性.

與其傳遞一個函數到其他函數中, **不如觀察一下是否有更專業的方式來解決你的問題. **

過度使用: 使用 lambda 進行非常簡單的操作

我們不說加法了, 再來說一下乘法吧:

不要在Python中編寫 lambda 表達式了

上面的 lambda 表達式是很有必要的, 因為不允許我們傳遞 *運算符, 就算它像一個函數一樣. 如果有一個等價於*的函數, 我們就可以將它傳遞給reduce函數.

Python 的標準庫實際上有一個完整的模塊來解決這個問題:

不要在Python中編寫 lambda 表達式了

Python 的運算符模塊讓 Python 的各種運算符像函數一樣易用. 如果你正在練習函數式編程, Python 的 operator模塊就是你的好助手.

除了提供與 Python 許多運算符相對應的函數以外, operator模塊還提供了一系列常用的更高級的函數來訪問條目和屬性, 以及調用方法.

itemgetter用來訪問列表/序列的索引或字典/映射的鍵值:

不要在Python中編寫 lambda 表達式了

attrgetter用來訪問對象的屬性:

不要在Python中編寫 lambda 表達式了

methodcaller用來調用對象的方法:

不要在Python中編寫 lambda 表達式了

我通常發現使用 operator模塊中的函數會使代碼看上去比使用等效的 lambda 表達式更加清晰易懂.

過度使用: 當給高階函數增加困惑時

一個接收其他函數作為參數的函數被稱為高階函數. 高階函數通常就是我們經常向其傳遞 lambda 函數的那一類函數.

在練習函數式編程時高階函數是很常用的. 然而函數式編程並不是應用 Python 思想的唯一方式: Python 是一種多範式語言, 因此我們可以混合併匹配編碼規則, 讓我們的代碼更有可讀性.

對比這個:

不要在Python中編寫 lambda 表達式了

和這個:

不要在Python中編寫 lambda 表達式了

第二段代碼長一些, 但是沒有函數式編程背景的人通常會覺得它更容易理解.

任何經歷過我的 Python 培訓課程的人可能都會理解 multiply_all函數的作用, 而對很多 Python 程序員來說,reduce/lambda的組合可能都會有些晦澀難懂.

通常, 將一個函數傳遞給另一個函數會使代碼更加複雜, 這不利於代碼的可讀性.

你應該使用 lambda 表達式嗎?

基於以下原因, 我覺得 lambda 表達式的應用是有問題的:

  • 對很多 Python 程序員來說, lambda 表達式是一種古怪而又陌生的語法

  • lambda 函數本身缺少名稱和文檔, 意味著了解它們功能的唯一方式就是讀代碼

  • lambda 表達式只能包含一條語句, 因此某些提高可讀性的語言功能, 如元組拆包, 不能與它們一起使用

  • lambda 函數通常可以被替換為標準庫中已存在的函數或 Python 內置的函數

比起一個命名良好的函數, lambda 表達式缺乏即刻可讀性. 儘管 def語句通常更容易理解,但 Python 還有很多可用於替換 lambda 表達式的功能, 包括特殊語法(推導), 內置函數(sum)和標準庫函數(在operator模塊中)

只有當你的情況完全滿足這四個標準時, 我才會說你可以使用 lambda 表達式:

  1. 你所要做的操作是不重要的: 函數不值得一個名稱

  2. 使用 lambda 表達式比你所能想到的函數名稱讓代碼更容易理解

  3. 你很確定還沒有一個函數能滿足你的需求

  4. 你團隊的每個人都了解 lambda 表達式, 並且都同意使用它們

如果上述四條中的任何一條都不符合你的情況, 我建議用 def來寫一個新的函數, (如果可能)接受一個在 Python 中已經存在且能滿足你需求的函數.


英文原文:http://treyhunner.com/2018/09/stop-writing-lambda-expressions/
譯者:郭明

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python部落 的精彩文章:

Python Unicode編碼混亂:來自大洋彼岸的怨念
關於Python的10條熱門twitter

TAG:Python部落 |