for Comprehension

April 14, 2022

如果使用者輸入的命令列引數是數字,想將這些數字全部進行平方運算,該怎麼做呢?現在的你,也許會想出這樣的寫法:

import sys

squares = []
for arg in sys.argv[1:]:
    squares.append(int(arg) ** 2)

print(squares)

map 映射

將一個 list 轉為另一個 list,是很常見的操作,Python 針對這類需求,提供了for Comprehension 語法,你可以如下實現需求:

import sys

squares = [int(arg) ** 2 for arg in sys.argv[1:]]
print(squares)

對於 for arg in sys.argv[1:] 這部份,其作用是逐一迭代出命令列引數指定給 arg 變數,之後執行 for 左方的 int(arg) ** 2 運算,使用[]含括起來,表示每次迭代的運算結果,會被收集為一個 list。一個執行結果如下:

>python square.py 10 20 30
[100, 400, 900]

filter 過濾

for Comprehension 也可以與條件式結合,這可以構成一個過濾的功能。例如想收集某個 list 中的奇數元素至另一 list,在不使用 for Comprehension下,可以如下撰寫:

import sys

odds = []
for arg in sys.argv[1:]:
    if int(arg) % 2:
        odds.append(arg)

print(odds)

若使用 for Comprehension 的話,可以改寫為以下的程式碼:

import sys

odds = [arg for arg in sys.argv[1:] if int(arg) % 2]
print(odds)

在這個例子中,只有在 if 條件式成立時,for 左邊的運算式才會被執行,並收集為最後結果 list 中的元素。一個執行結果如下:

>python odds.py 11 8 9 5 4 6 3 2
['11', '9', '5', '3']

map 映射、filter 過濾,其實是具有函數式風格的模式,有興趣可以看看〈Map〉、〈Filter〉。

for Comprehension 如果要形成巢狀結構也是可行的,不過建議別太過火,不然可讀性會迅速降低。簡單地將矩陣表示為一維的 list 倒還不錯:

>>> matrix = [
...     [1, 2, 3],
...     [4, 5, 6],
...     [7, 8, 9]
... ]
>>> array = [element for row in matrix for element in row]
>>> array
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

另一個例子是,使用 for Comprehension 來取得兩個序列的排列組合:

>>> [letter1 + letter2 for letter1 in 'Justin' for letter2 in 'momor']
['Jm', 'Jo', 'Jm', 'Jo', 'Jr', 'um', 'uo', 'um', 'uo', 'ur', 'sm', 'so', 'sm', 'so', 'sr', 'tm', 'to', 'tm', 'to', 'tr', 'im', 'io', 'im', 'io', 'ir', 'nm', 'no', 'nm', 'no', 'nr']
>>>

惰性求值

for Comprehension 兩旁放上 [],表示會產生 list,如果資料來源很長,或者資料來源本身,是個有惰性求值特性的產生器時,直接產生 list 顯得沒有效率,這時可以在 for Comprehension兩旁放上 (),這樣的話就會建立一個 generator 物件,具有惰性求值(lazy evaluation)特性。

舉個例子來說,Python 中有個 sum 函式,可以計算指定序列的數字加總值,像是若傳遞 sum([1, 2, 3]) 的話,結果會是 6。如果想計算 1 到 10000 的加總值呢?使用 sum([n for n in range(1, 10001)]) 是可以達到目的,不過,這會先產生具有 10000 個元素的 list,然後再交給 sum 函式運算,此時可以寫成 sum(n for n in range(1, 10001)),就不會有產生 list 的負擔。

這邊其實也在說明,只要寫 n for n in range(1, 10001) 就是個產生器運算式了,因此在傳給 sum 函式時,不必再寫成 sum((n for n in range(1, 10001))),需要加上括號的情況,是在需要直接參考一個產生器的時候,例如 g = (n for n in range(1, 10001)) 的情況。

應用於 set/dict/tuple

for Comprehension 也可用來建立 set,只要在 for Comprehension兩旁放上 {}。例如,建立一個 set,其中包括了來源字串中不重複的大寫字母。

>>> text = 'Your Right brain has nothing Left. Your Left brain has nothing Right'
>>> {c for c in text if c.isupper()}
{'Y', 'R', 'L'}
>>>

若是想使用 for Comprehension 來建立 dict 實例,也是可行的。例如:

>>> names = ['Justin', 'Monica', 'Irene']
>>> passwds = [123456, 654321, 13579]
>>> {name : passwd for name, passwd in zip(names, passwds)}
{'Justin': 123456, 'Irene': 13579, 'Monica': 654321}
>>>

上面的 zip,將 namespasswds 兩兩相扣在一起成為 tuple,每個 tuple 中的一對元素,會在 for Comprehension 中拆解指定給 namepasswd,最後 namepasswd 組成 dict 的每一對鍵值。

那麼,可以使用 for Comprehension建立 tuple嗎?可以的,不過不是在 for Comprehension兩旁放上 (),這樣的話就會建立一個 generator 物件,而不是 tuple,想要用 for Comprehension 建立 tuple 的話,可以將 for Comprehension 產生器運算式傳給 tuple。例如:

>>> tuple(n for n in range(10))
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
>>>

分享到 LinkedIn 分享到 Facebook 分享到 Twitter