Python列表解析式總結

前言

目錄

前言

什麼是列表解析式?

從循環到解析式

列表解析式:可視化解讀

無條件子句的列表解析式

嵌套循環

其他解析式

還要注意可讀性

列表解析式 List comprehension

帶嵌套循環的列表解析式

字典解析式

總結

補充:if ... else用法


有時候,一個編程設計模式使用得十分普遍,甚至會逐步形成自己獨特的語法。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循環中複製粘貼,稍微調整一下就變成了列表解析式啦。

for loop to list comprehension

下面是我們複製粘貼的順序:

  • 將變量賦值操作複製到新建的空列表中(第三行)
  • 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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章