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']
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,將 names 與 passwds 兩兩相扣在一起成為 tuple,每個 tuple 中的一對元素,會在 for Comprehension 中拆解指定給 name 與 passwd,最後 name 與 passwd 組成 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)
>>>


