有時候,如果一種程式設計設計模式想要“捍衛”自己獨特的語法形態不被改變,就會把自己變成普遍的流行趨勢。Python 的列表推導就是這樣一個典型的語法糖的例子。
Python 中的列表推導是非常棒的,但是要精通它們就比較困難了,因為它不是在解決一個新的問題:它只是提供了一種新的語法來解決一個已經存在的問題。
讓我們來學習一下什麼是列表推導,以及如何辨別什麼時候該使用列表推導。
什麼是列表推導?
列表推導是一個將一個列表(實際上是任意可迭代物件)轉換成另一個列表的工具。在轉換時,每個元素都可以按照某個條件被包含在新的列表中,並根據需要做出一些變換。
如果你熟悉函數語言程式設計,你可以把列表推導想成是一個filter
後面跟了一個map的語法糖
:
1 2 |
>>> doubled_odds = map(lambda n: n * 2, filter(lambda n: n % 2 == 1, numbers)) >>> doubled_odds = [n * 2 for n in numbers if n % 2 == 1] |
如果你不熟悉函數語言程式設計,別擔心,我會用for
迴圈來解釋。
從迴圈到列表推導
每個列表推導都可以重寫成for
迴圈的形式,但並不是每一個for
迴圈都可以重寫成列表推導。
要理解什麼時候該使用列表推導,關鍵在於不斷練習辨別哪些問題看起來像是列表推導。
如果你能把你的程式碼重寫成這個樣子的for
迴圈,那你就能把它重寫成列表推導:
1 2 3 4 |
new_things = [] for ITEM in old_things: if condition_based_on(ITEM): new_things.append("something with " + ITEM) |
你可以把上面的for
迴圈重寫成這個樣子的列表推導:
1 |
new_things = ["something with " + ITEM for ITEM in old_things if condition_based_on(ITEM)] |
列表推導:動態圖™
看起來不錯,不過具體要怎麼做呢?
我們用複製貼上來把一個for
迴圈變成列表推導。
下面是複製貼上的順序:
- 複製給新列表賦值的語句(第三行)
- 把我們
append
到新列表的表示式複製過來(第六行) - 複製
for
迴圈的那一行,除去末尾的:
(第四行) - 複製
if
語句,除去末尾的:
(第五行)
現在我們就把下面這個for 迴圈:
1 2 3 4 5 6 |
numbers = [1, 2, 3, 4, 5] doubled_odds = [] for n in numbers: if n % 2 == 1: doubled_odds.append(n * 2) |
變成了這個樣子:
1 2 3 |
numbers = [1, 2, 3, 4, 5] doubled_odds = [n * 2 for n in numbers if n % 2 == 1] |
列表推導:現在加上顏色
我們來給程式碼加上高亮。
doubled_odds = []
for n in numbers:
if n % 2 == 1:
doubled_odds.append(n * 2)
doubled_odds = [n * 2 for n in numbers if n % 2 == 1]
- 複製給新列表賦值的語句
- 把我們
append
到新列表的表示式複製過來 - 複製
for
迴圈的那一行,除去末尾的:
- 複製
if
語句,同樣去掉:
無條件列表推導
但如果是沒有條件語句(就是末尾的那個if SOMETHING
)的情形呢?這些迴圈新增元素的for
迴圈比我們剛剛講過的那種迴圈並根據條件新增元素的要更簡單。
沒有if
語句的for
迴圈:
1 2 3 |
doubled_numbers = [] for n in numbers: doubled_numbers.append(n * 2) |
同樣的程式碼寫成列表推導:
1 |
doubled_numbers = [n * 2 for n in numbers] |
下面是變換的動態圖:
我們可以按照下列步驟從for
迴圈裡複製貼上:
- 複製給新列表賦值的語句(第三行)
- 把我們
append
到新列表的表示式複製過來(第五行) - 複製
for
迴圈的那一行,除去末尾的:
(第四行)
巢狀迴圈
如果是帶有巢狀迴圈的列表推導呢?……
這是把一個矩陣平鋪成向量的for
迴圈:
1 2 3 4 |
flattened = [] for row in matrix: for n in row: flattened.append(n) |
這是相應的列表推導:
1 |
flattened = [n for row in matrix for n in row] |
列表推導裡的巢狀迴圈讀起來並不像是英語散文那樣通俗易懂。
注意:我在腦袋裡想把這個列表推導寫成這個樣子:
1 |
flattened = [n for n in row for row in matrix] |
但是這是不對的!這裡我錯把兩個迴圈顛倒了,上面的那個才是正確的。
在處理列表推導裡的巢狀for
迴圈時,要記住:for
語句的順序和原來的迴圈中for
語句的順序是一樣的。
其他推導式
這個原則同樣適用於集合推導和字典推導。
下面的程式碼建立了一個由一個序列中所有單詞的首字母組成的集合:
1 2 3 |
first_letters = set() for w in words: first_letters.add(w[0]) |
寫成集合推導:
1 |
first_letters = {w[0] for w in words} |
建立一個新的字典把原來字典的值和鍵值交換的程式碼:
1 2 3 |
flipped = {} for key, value in original.items(): flipped[value] = key |
寫成字典推導:
1 |
flipped = {value: key for key, value in original.items()} |
可讀性很重要
你有沒有發現上面的這些列表推導可讀性比較差?當那些比較長的列表推導寫在一行裡的時候,我經常覺得它們很難讀懂。
記住,Python 允許在括號之間進行換行。
列表推導
換行前
1 |
doubled_odds = [n * 2 for n in numbers if n % 2 == 1] |
換行後
1 2 3 4 5 |
doubled_odds = [ n * 2 for n in numbers if n % 2 == 1 ] |
列表推導中的巢狀迴圈
換行前
1 |
flattened = [n for row in matrix for n in row] |
換行後
1 2 3 4 5 |
flattened = [ n for row in matrix for n in row ] |
字典推導
換行前
1 |
flipped = {value: key for key, value in original.items()} |
換行後
1 2 3 4 |
flipped = { value: key for key, value in original.items() } |
注意,我們不是隨便換行的:在寫列表推導的時候我們複製貼上了很多條語句,我們是在這些語句之間換行的。在用各種顏色標註的版本中,我們是在顏色發生變化時換行的。
和我一起學
我最近在 PyLadies Remote 上開設了關於列表推導的課程。
如果你想聽我講以上任何一個主題,請檢視以下視訊:(譯註:以下視訊均位於 Youtube)
總結
當你努力去寫一個推導式的時候,不要慌,從複製貼上for
迴圈中的內容著手。
任何一個這樣形式的迴圈:
1 2 3 4 |
new_things = [] for ITEM in old_things: if condition_based_on(ITEM): new_things.append("something with " + ITEM) |
都可以重寫成這樣的推導式:
1 |
new_things = ["something with " + ITEM for ITEM in old_things if condition_based_on(ITEM)] |
如果你能把一個for
迴圈變形成上面的樣子,你就可以把它重寫成一個推導式。