Python入門必備:細講Python推導(dǎo)式
由于Python的良好生態(tài),很多時候我們的程序只是通過調(diào)用別人寫好的方法即可實現(xiàn)功能。
不過,很多時候我們還是需要處理序列。不管是入門中還是早已入門的小伙伴,對于處理序列毫無疑問會選擇用for循環(huán)。但在Python中還有一種更高效更簡潔的處理序列方式——推導(dǎo)式。本文詳細探討關(guān)于推導(dǎo)式的細節(jié)。
for循環(huán)有啥不好,非要學(xué)推導(dǎo)式?
我們來看一個例子,如何把一個數(shù)值列表中大于0的數(shù)值篩選出來。下圖給出for循環(huán)的做法
可以看到for循環(huán)還是妥妥地把問題解決,代碼并不復(fù)雜。
分析代碼與原問題的表達對應(yīng)關(guān)系:
- 行5,表達從數(shù)值列表取出數(shù)值。
- 行6,表達"大于0的數(shù)值篩選出來"
- 但原問題沒有提及到創(chuàng)建一個用于保存結(jié)果的列表和如何把結(jié)果加入結(jié)果列表。
- 上面的代碼中的行4與行7,都是多余的動作。
是時候讓推導(dǎo)式出場了
上圖是一種比較"官方"的寫法,把整個推導(dǎo)式寫到一行里,我更喜歡下圖的寫法。
這就是列表推導(dǎo)式,很簡單吧??雌饋砥鋵嵟c之前的for循環(huán)寫法差不多。但推導(dǎo)式有以下好處::
- 不需要像for循環(huán)那樣,先定義一個列表,然后在循環(huán)中編寫如何把結(jié)果放入列表的代碼。
- 表達更為清晰了,推導(dǎo)式的每個部分都與原問題的表達一一對應(yīng)。
- 行15,表達了 我要把什么樣的東西放入結(jié)果中,這里只有一個n,表示符合要求的數(shù)值。
- 行16與行17與之前for循環(huán)分析是一致。注意看,這里不再需要寫冒號了。
- 推導(dǎo)式的外面用一個[]包圍著,表示結(jié)果是一個列表。
- 推導(dǎo)式的性能更好。在序列的數(shù)據(jù)量不大的情況下推導(dǎo)式的性能優(yōu)勢不會太明顯,如果序列的元素數(shù)量成千上萬,那么推導(dǎo)式比for形式性能通常優(yōu)勝2倍以上。
通過對比學(xué)習(xí)推導(dǎo)式
覺得怎么樣,推導(dǎo)式是不是語義表達好的同時性能又高呢。下面通過與for循環(huán)形式對比來學(xué)習(xí)推導(dǎo)式的寫法。
圖中左邊是for循環(huán),右邊是推導(dǎo)式:
紅框部分表示遍歷序列,可以看到兩者形式一樣,但注意,推導(dǎo)式不需要在最后寫冒號:
同樣地,上圖紅框表示如何判斷每個元素,這里表示過濾的條件。
我們可以寫各種各樣復(fù)雜的判斷條件。
上圖紅框是推導(dǎo)式最后一部分,他決定了輸出結(jié)果
比如說,如果希望每個輸出值是原來的兩倍,我們就可以寫 n*2
結(jié)果可以是各種各樣的類型,比如紅框部分如果寫 f'值:{n}',那么結(jié)果就是一系列的字符串。
更進一步
我們來看一個稍微復(fù)雜點的例子。
假設(shè)我們有多個文件,每個文件都有多行數(shù)值(都是整數(shù)),行數(shù)不確定。如圖:
現(xiàn)在需要把多個這樣的文件的所有數(shù)值拿出來,然后把小于50的數(shù)值篩選出來作為結(jié)果,并且標注每個數(shù)值的來源文件。
下圖是基本數(shù)據(jù)的定義:
方法 get_nums_from_file 不是這里的重點,我們只需要知道,給他一個文本路徑,他會讀取文件中的每行的整數(shù),以返回一個整數(shù)列表
這個問題可以描述為"列表中的元素還可以提取出一個列表",這樣的情況下同樣可以用推導(dǎo)式。如下:
- 其實與普通的for循環(huán)嵌套是差不多的套路
- 行38,首先遍歷paths列表
- 行39,在上一個循環(huán)中獲取文件中的整數(shù)列表再次遍歷這個整數(shù)列表
- 行40,對應(yīng)原問題的篩選條件。
- 行37,這里可以使用下方兩個for的變量f和n,因此可以輕而易舉找到每個數(shù)值的來源
有時候不應(yīng)該強行使用推導(dǎo)式
我們很容易犯的一個錯誤是,手上拿著一個錘子,看啥都認為是釘子,更何況拿著的是一個雷神之錘。
推導(dǎo)式簡潔又高效的好處,很容易讓人著迷于使用他來解決一切的集合處理問題。我們接著上面的需求來說明。
現(xiàn)在需求不僅僅過濾小于50的數(shù)值,而是取出"小于所在文件的所有數(shù)值的平均",并且結(jié)果需要顯示該文件的平均值。
下面是推導(dǎo)式的解決方法:
推導(dǎo)式最大的問題在于無法在過程中建立臨時變量
這個需求下,由于沒法用臨時變量保存一個文件的平均值,因此導(dǎo)致多次求平均,不僅代碼結(jié)構(gòu)亂,而且效率還很低。
這時候老老實實使用for循環(huán)是個很好的選擇。如下:
未來,推導(dǎo)式可能的進化
Python的推導(dǎo)式其實來源于函數(shù)式編程中的思想,目前市面上的幾門面向?qū)ο缶幊陶Z言都加入了相關(guān)方面的語法,未來Python的推導(dǎo)式可能會參考他們從而改進自身的推導(dǎo)式語法。如圖為C#的Linq,特點是他允許在過程中定義臨時變量。
可以看到,如果Python的推導(dǎo)式加入這樣的語法功能,那么本文說的推導(dǎo)式的缺點就不再出現(xiàn)。Python的推導(dǎo)式在未來的進化值得期待。
小結(jié)
- 在處理序列時,推導(dǎo)式是一個高效簡潔的方式
- 當需求需要在循環(huán)中創(chuàng)建各種臨時的狀態(tài)數(shù)據(jù)時,推導(dǎo)式就不再適合處理。建議考慮使用for循環(huán)。
在Python中,推導(dǎo)式很多時候被當作是否熟悉Python的標志之一,同時推導(dǎo)式也存在許多爭議,我們應(yīng)該清楚了解推導(dǎo)式再談如何應(yīng)用,畢竟任何技術(shù)都必需在適當?shù)牡胤讲拍馨l(fā)揮最大的作用。
你已經(jīng)學(xué)會了推導(dǎo)式了嗎?平時使用for循環(huán)比較多還是推導(dǎo)式比較多?