前言
目錄
有時候,一個編程設計模式使用得十分普遍,甚至會逐步形成自己獨特的語法。Python編程語言中的列表解析式(list comprehension)就是這類語法糖(syntactic sugar)的絕佳代表。
Python中的列表解析式是個偉大的發明,但是要掌握好這個語法則有些難,因爲它們並是用來解決全新的問題:只是爲解決已有問題提供了新的語法。
什麼是列表解析式?
列表解析式是將一個列表(實際上適用於任何可迭代對象(iterable))轉換成另一個列表的工具。在轉換過程中,可以指定元素必須符合一定的條件,才能添加至新的列表中,這樣每個元素都可以按需要進行轉換。
如果你熟悉函數式編程(functional programming),你可以把列表解析式看作爲結合了filter
函數與map
函數功能的語法糖:
>>> 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
循環都能重寫爲列表解析式。
掌握列表解析式使用時機的關鍵,在於不斷練習識別那些看上去像列表解析式的問題(practice identifying problems that smell like list comprehensions)。
如果你能將自己的代碼改寫成類似下面這個for
循環的形式,那麼你也就可以將其改寫爲列表解析式:
:::python
new_things = []
for ITEM in old_things:
if condition_based_on(ITEM):
new_things.append("something with " + ITEM)
你可以將上面的for
循環改寫成這樣的列表解析式:
:::python
new_things = ["something with " + ITEM for ITEM in old_things if condition_based_on(ITEM)]
列表解析式:可視化解讀
可視化解讀聽上去是個不錯的注意,但是我們怎麼才能做到這點呢?
嘿嘿,只需要從for
循環中複製粘貼,稍微調整一下就變成了列表解析式啦。
下面是我們複製粘貼的順序:
- 將變量賦值操作複製到新建的空列表中(第三行)
- 將
append()
方法中的表達式參數複製到新列表中(第六行) - 複製
for
循環語句,不包括最後的:
(第四行) - 複製
if
條件控制語句,同樣不包括最後的:
(第五行)
這樣,我們將從下面這段代碼:
:::python
numbers = [1, 2, 3, 4, 5]
doubled_odds = []
for n in numbers:
if n % 2 == 1:
doubled_odds.append(n * 2)
轉換成了這兩行代碼:
:::python
numbers = [1, 2, 3, 4, 5]
doubled_odds = [n * 2 for n in numbers if n % 2 == 1]
無條件子句的列表解析式
如果是那些沒有條件子句(即if SOMETHING
部分)的代碼呢,又該怎樣複製粘貼?這些形式的代碼甚至比有條件子句的代碼更好實現。
一個沒有if
語句的for
循環:
:::python
doubled_numbers = []
for n in numbers:
doubled_numbers.append(n * 2)
上面這段代碼頁可以改寫爲一個列表解析式:
:::python
doubled_numbers = [n * 2 for n in numbers]
我們可以從上面那個簡單的for
循環中,按照這樣的順序複製粘貼:
- 將變量賦值操作複製到新建的空列表中(第三行)
- 將
append()
方法中的表達式參數複製到新列表中(第五行) - 複製
for
循環語句,不包括最後的:
(第四行)
嵌套循環
那麼嵌套循環(nested loop)又該怎樣改寫爲列表解析式呢?
下面是一個拉平(flatten)矩陣(以列表爲元素的列表)的for
循環:
:::python
flattened = []
for row in matrix:
for n in row:
flattened.append(n)
下面這個列表解析式實現了相同的功能:
:::python
flattened = [n for row in matrix for n in row]
列表解析式中的嵌套循環讀起來就有點繞口了。
注意:我本能地會想把這個列表解析式寫成這樣:
:::python
flattened = [n for n in row for row in matrix]
但是這行代碼是錯誤的。這裏我不小心顛倒了兩個for
循環的順序。正確的代碼是之前那個。
如果要在列表解析式中處理嵌套循環,請記住for
循環子句的順序與我們原來for
循環的順序是一致的。
同樣地原則也適用集合解析式(set comprehension)和字典解析式(dictionary comprehension)。
其他解析式
下面的代碼提取單詞序列中每個單詞的首字母,創建了一個集合(set):
:::python
first_letters = set()
for w in words:
first_letters.add(w[0])
同樣的代碼可以改寫爲集合解析式:
:::python
first_letters = {w[0] for w in words}
下面的代碼將原有字典的鍵和值互換,從而創建了一個新的字典:
:::python
flipped = {}
for key, value in original.items():
flipped[value] = key
同樣的代碼可以改寫爲字典解析式:
:::python
flipped = {value: key for key, value in original.items()}
還要注意可讀性
你有沒有發現上面的列表解析式讀起來很困難?我經常發現,如果較長的列表解析式寫成一行代碼,那麼閱讀起來就非常困難。
不過,還好Python支持在括號和花括號之間斷行。
列表解析式 List comprehension
斷行前:
:::python
doubled_odds = [n * 2 for n in numbers if n % 2 == 1]
斷行後:
:::python
doubled_odds = [
n * 2
for n in numbers
if n % 2 == 1
]
帶嵌套循環的列表解析式
斷行前:
:::python
flattened = [n for row in matrix for n in row]
斷行後:
:::python
flattened = [
n
for row in matrix
for n in row
]
字典解析式
斷行前:
:::python
flipped = {value: key for key, value in original.items()}
斷行後:
:::python
flipped = {
value: key
for key, value in original.items()
}
請注意,我們並不是隨意進行斷行:我們是在每一行復制過來的代碼之後斷行的。
總結
糾結於寫不出列表解析式嗎?不要擔心。先寫一個for
循環,能後按照本文說的順序複製粘貼,就可以寫出解析式了。
任何類似下面代碼形式的for
循環:
:::python
new_things = []
for ITEM in old_things:
if condition_based_on(ITEM):
new_things.append("something with " + ITEM)
都可以被改寫爲下面這種列表解析式:
:::python
new_things = ["something with " + ITEM for ITEM in old_things if condition_based_on(ITEM)]
(有刪減)
原文鏈接:https://treyhunner.com/2015/12/python-list-comprehensions-now-in-color/
補充:if ... else用法
使用列表生成式的時候,有些童鞋經常搞不清楚if...else
的用法。
例如,以下代碼正常輸出偶數:
>>> [x for x in range(1, 11) if x % 2 == 0]
[2, 4, 6, 8, 10]
但是,我們不能在最後的if
加上else
:
>>> [x for x in range(1, 11) if x % 2 == 0 else 0]
File "<stdin>", line 1
[x for x in range(1, 11) if x % 2 == 0 else 0]
^
SyntaxError: invalid syntax
這是因爲跟在for
後面的if
是一個篩選條件,不能帶else
,否則如何篩選?
另一些童鞋發現把if
寫在for
前面必須加else
,否則報錯:
>>> [x if x % 2 == 0 for x in range(1, 11)]
File "<stdin>", line 1
[x if x % 2 == 0 for x in range(1, 11)]
^
SyntaxError: invalid syntax
這是因爲for
前面的部分是一個表達式,它必須根據x
計算出一個結果。因此,考察表達式:x if x % 2 == 0
,它無法根據x
計算出結果,因爲缺少else
,必須加上else
:
>>> [x if x % 2 == 0 else -x for x in range(1, 11)]
[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10]
上述for
前面的表達式x if x % 2 == 0 else -x
才能根據x
計算出確定的結果。
可見,在一個列表生成式中,for
前面的if ... else
是表達式,而for
後面的if
是過濾條件,不能帶else
。