當前位置:
首頁 > 知識 > 列表推導和生成器表達式的濫用

列表推導和生成器表達式的濫用

雖然我很喜歡列表推導,但我發現一旦新的Pythonistas開始真正欣賞、理解之後,他們往往會在很多地方使用它。 列表推導雖然很有用,但很容易被濫用!

本文旨在討論推導不是工作的最佳工具的情況,至少在可讀性方面。 我們將討論一些更容易理解的替代方案,我們也會看到一些不那麼明顯的情況,根本不需要推導。

如果你還不是列表推導的「粉絲」那麼這篇文章並不是為了嚇唬你。 這是為了讓那些需要它的我們(包括我自己)適度使用。

注意:注意:在本文中,我將使用術語「推導」來指代所有形式的推導(list,set,dict)以及生成器表達式。 如果你不熟悉推導,我建議你閱讀這篇文章或者觀看這個演講(這個演講更深入地探討了生成器表達式)。


用很少的間距寫推導式

列表推導式的批評聲都在說,它們的可讀性太差了。他們是正確的,許多推導式的可讀性都很差,有時,一個推導式想要變得可讀性更好,只需要更好的間距。

例如,先看一下下面的代碼:

列表推導和生成器表達式的濫用

通過添加一些放置良好的換行符,我們可以使推導式的可讀性更好一些。

列表推導和生成器表達式的濫用

更少的代碼意味著更好的可讀性,但也不是一直如此。空格在你寫推導式的時候十分重要。

一般來說,我更喜歡使用上面的縮進樣式將大多數推導式用多行代碼劃分出來。


寫奇怪的推導式

一些循環在技術上可以被寫成推導式,但是他們存在太多邏輯,也許他們並不適合寫成推導式。看一下下面的代碼:

列表推導和生成器表達式的濫用

上面這個推導式等價於下面這個for循環:

列表推導和生成器表達式的濫用

推導式和for循環都使用三個嵌套的內聯if語句(Python的三元運算符)。

這裡有一個更加可讀的方式來寫代碼,使用if-elif-else結構。

列表推導和生成器表達式的濫用

僅僅是有一種方法可以將你的代碼寫成推導式,這並不意味著你應該將你的代碼寫成推導式。

在推導式中要小心使用任何數量的複雜邏輯,即使是單個內聯if:

列表推導和生成器表達式的濫用

如果你真的更喜歡在這樣的情況下使用推導式,至少要考慮空格或括弧是否可以使代碼更具可讀性:

列表推導和生成器表達式的濫用

並且考慮是否將一些邏輯分解為單獨的函數,這樣也可以提高可讀性。

列表推導和生成器表達式的濫用

單獨的函數是否使事物更具可讀性將取決於操作的重要性,操作的規模以及函數名稱傳達操作的程度。


循環偽裝成推導式

有時會遇到使用推導式語法的代碼,但是卻破壞了推導式的使用精神。

舉個例子,下面的代碼看起來像推導式

列表推導和生成器表達式的濫用

但它並不像是一種推導式。 我們正在將推導用於其不適合的目的。

如果我們在Python shell中執行這個推導式,你會明白我的意思。它除了會列印出1到10的數字之外,還會列印出一堆None。

我們想列印出1到10之間的所有數字,這就是我們所做的。 但是這個推導式也向我們返回了一個無值的列表。

推導式用於構建列表,這是它們的作用。我們從print函數建立了一個返回值列表,print函數返回None。但是我們並不關心我們的推導式所建立的列表,我們只關心它的副作用。

我們可以這樣寫代碼:

列表推導和生成器表達式的濫用

列表推導式用於循環遍歷可迭代序列並構建新列表,而for循環用於循環遍歷可迭代序列以執行您想要的任何操作。

當我在代碼中看到列表推導式時,我立即假設我們正在構建一個新列表(因為這就是它們的用途)。 如果您將推導式用於構建新列表之外的目的,則會使閱讀代碼的其他人感到困惑。所以,如果你不是想構建一個新的列表,不要使用推導式。


在存在更合適的工具時使用推導式

對於許多問題,更特殊的工具比循環的通用目的更有意義。 但是推導式並不總是最適合手頭工作的專用工具。

有這樣一段代碼:

列表推導和生成器表達式的濫用

這種理解唯一目的是遍歷給定的可迭代文件(csv.reader(csv_file))並從中創建一個列表。但是在python中,我們有更加合適的工具來實現。Python列表構造函數可以為我們完成所有循環和列表創建工作,如下所示(第二行開頭應該是lines = list(...)):

列表推導和生成器表達式的濫用

推導式是一種特殊用途工具,用於循環遍歷迭代以構建新列表,同時修改過程中的每個元素或過濾元素。 列表構造函數是一個專用工具,用於循環遍歷迭代序列以構建新列表,而不會更改任何內容。

如果你不需要在列表構建過程中過濾你的元素,或者將它們變成新的元素,那麼你不需要列表推導式,而需要列表構造函數。

下面這段代碼,這種推導式將我們從zip循環中獲得的每個行元組轉換為列表:

列表推導和生成器表達式的濫用

我們也可以通過列表構造函數來實現:

列表推導和生成器表達式的濫用

無論何時你看到推導式是像這樣的:

列表推導和生成器表達式的濫用

你都可以將其替換成如下代碼:

列表推導和生成器表達式的濫用

同樣適用於字典、集合推導式。

這裡有一段之前我寫過的代碼(倒數第二行是 for abbreviation ....):

列表推導和生成器表達式的濫用

在這裡,我們循環遍歷兩項元組的列表並從中創建字典。這個任務正是dict構造函數的用途

列表推導和生成器表達式的濫用

內置列表和字典構造函數不是唯一的推導式替換工具。 標準庫和第三方庫還包括有時比推導式工具更適合您的循環需求的工具。這是一個生成器表達式,它將一個可迭代序列中的所有元素相加:

列表推導和生成器表達式的濫用

以下是使用itertools.chain完成相同的事情;

列表推導和生成器表達式的濫用

當你應該使用替代方案並不總是直截了當時,你應該使用推導式。我經常糾結於使用itertools.chain還是推導式。 我通常以兩種方式編寫代碼,然後選擇看起來更清晰的代碼。可讀性在很多編程結構中都是特定於問題的,包括推導式。


不必要的工作

有時您會看到不應被其他構造替換的推導式,而應完全刪除,只留下它們循環的迭代序列。

這裡我們打開一個單詞文件(每行一個單詞),將文件存儲在內存中,並計算每次出現的次數:

列表推導和生成器表達式的濫用

這裡我們使用生成器表達式,也可以。

列表推導和生成器表達式的濫用

在將它傳遞給Counter類之前,我們循環遍歷列表以將其轉換為生成器。 那是不必要的工作! Counter類接受任何可迭代序列它不關心它們是列表,生成器,元組還是其他東西。

這是另一種不必要的推導式:

列表推導和生成器表達式的濫用

如果我們要做的就是遍歷它一次,沒有理由將迭代序列轉換為列表。在Python中,我們通常不關心某些東西是否是列表,而是更關心它是否是可迭代的。當你不需要時,小心不要創建新的迭代:如果你只想循環迭代序列一次,只需使用你已經擁有的迭代序列。


我什麼時候才能用推導式

簡單但不精確的答案是,只要您能夠以下面的格式編寫代碼,並且沒有其他工具可以用來縮短代碼,您應該考慮使用列表推導。

列表推導和生成器表達式的濫用

這個循環可以寫成如下推導式:

列表推導和生成器表達式的濫用

複雜的答案是,只要推導式有意義,你就應該考慮它們。 這不是一個真正的答案,但是「我應該何時使用推導式」這一問題沒有一個答案?例如,這是一個for循環,它看起來並不像是可以使用推導式來重寫:

列表推導和生成器表達式的濫用

這裡也有另外一種方法:使用構造函數:all函數:

列表推導和生成器表達式的濫用

再如下面這段代碼:

列表推導和生成器表達式的濫用

那裡沒有append,也沒有建立新的迭代序列。 但是如果我們創建一個正方形生成器,我們可以將它們傳遞給內置sum函數以獲得相同的結果:

列表推導和生成器表達式的濫用

因此,除了「我可以按我的方式從循環複製粘貼到推導式」檢查之外,還有另一個更模糊的檢查:可以通過生成器表達式與可迭代接受函數或類相結合來增強代碼嗎?任何接受可迭代序列作為參數的函數或類都可能是與生成器表達式組合的良好候選者。


小心使用列表推導式

列表推導式可以使你的代碼更具可讀性,但它們肯定會被濫用。

列表推導是用於解決特定問題的專用工具。 list和dict構造函數是用於解決更具體問題的更加特殊用途工具。

當您遇到的問題不符合推導式的適用範圍或其他特殊用途的循環工具時,循環是一種更通用的工具。

像any,all和sum這樣的函數,以及像Counter和chain這樣的類是可接受迭代序列的工具,它們很好地與推導式配對,有時可以完全取代推導式。

請記住,推導式是出於一個目的:從舊的可迭代序列創建一個新的可迭代序列,同時稍微調整值或過濾掉與某個條件不匹配的值。推導式是一個有用的工具,但它們不是你唯一的工具。不要忘記列表和字典構造函數,並在您的推導式不合適時始終考慮循環。


英文原文:https://treyhunner.com/2019/03/abusing-and-overusing-list-comprehensions-in-python/ 譯者:assasin

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

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


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

互聯網巨頭們在測試介面中泄露的商業機密
VS Code中的Python –2019年3月發布

TAG:Python部落 |