要數 Python 其中一個最吸引我的地方,就是能夠將編程的 For Loop 極度簡化成一行淺白的 Code。對於十分懶惰的我,無疑是個解脫。這個特別的編程語法名為 List Comprehension。
簡單而言,List Comprehension 是一個重整列表的方法。透過原來的列表,加以一些簡單的處理,去製做一個新的列表。在大部分其他的編程語言,例如 C、Java、VB 等,通常我們需要明確地 (Explicitly) 寫出 For Loop ,但在 Python 編程裡,可以透過隱密的 (Implicit)語法達至相同效果。
除了簡單處理以外,List Comprehension 亦能用以製做字典(Dictionary)、元組(Tuple)等可疊代(iterable)的資料結構,又可以自訂功能,用途可能遠比您想像大!
列表 List Comprehension
列表(List)
最原始的 List Comprehension 例子是將一個 List 轉換成另外一個 List。編程語法是:
output_list = [func(x) for x in input_list]
我們先看看以下例子:
my_list = ['士多', '啤梨', '啤梨', '蘋果', '橙']
output = ['生果: ' + x for x in my_list]
print(output)
在這個例子,我們簡單地在每款生果上標記「生果」。對比起傳統的 For Loop,List Comprehension 的語法:
output = ['生果: ' + x for x in my_list]
更加直接了當。語法的括號代表我們輸出成 List,而 For each 的語法則跟隨每個 element 之後,代表我們隱密地(Implicitly)使用了 For loop。
字典(Dictionary)
另外比較常見的 List Comprehension 可以用於製作字典。字典(Dictionary)多數用作一些 item-attribute 的情況,例如 key 是一班學生,attribute 是他們的身高、體重、血型等。編程語法是:
output_dict = {key(x): item(x) for x in input_list}
我們看看以下例子:
from pprint import pprint
my_list = ['士多', '啤梨', '啤梨', '蘋果', '橙']
output = {x: {'第一個字': x[0], '類別': '生果'} for x in my_list}
pprint(output, width=1)
在這個例子,我們製做的字典輸出 2 個屬性:生果的第一個字(例如:蘋果 -> 蘋)、以及類別(生果)。
留意 output 只有 4 個 item,因為字典只能接受獨特(Unique)的 key,所以只有 1 個啤梨而非 2 個啤梨。
元組(Tuple)
元組(Tuple)則用於您想限制有多少 item 的情況,例如 DSE 成績只能是(5**,5*,5,4,3,2,1,U)的 8 個可能性。 編程語法是:
output_tuple = tuple([func(x) for x in input_list])
留意我們需要先製做列表,再以 tuple()
轉換成元組。我們看看以下例子:
my_list = ['士多', '啤梨', '啤梨', '蘋果', '橙']
output = tuple([len(x) for x in my_list])
print(output)
透過 List Comprehension,我們輕鬆地將每款生果的名稱字數(例如蘋果 -> 2)輸出成為列表,再轉換成元組。
字典(Dict Comprehension)
另一個非常好用的型態是 Dictionary Comprehension,就是我們的 input 是字典而非列表。編程語法是:
output = {key(k): item(v) for k, v in input_dict.items()}
特別是處理 JSON 類的數據時(例如包含學生的身高、體重、血型等許多 attribute),我們只需要抽取數據裡的其中一項(attribute), Dict Comprehension 就會非常好使。
我們看看以下例子:
from pprint import pprint
my_dict = {
'啤梨': {'第一個字': '啤',
'類別': '生果'},
'士多': {'第一個字': '士',
'類別': '生果'},
'橙': {'第一個字': '橙',
'類別': '生果'},
'蘋果': {'第一個字': '蘋',
'類別': '生果'}
}
output = {k: v['第一個字'] for k, v in my_dict.items()}
pprint(output, width=1)
我們使用先前的例子生成的字典,抽取裡面的其中一項。可見 Dict Comprehension 是一種壓縮(Compress)字典的好方法。
條件式 List Comp(If-else)
List Comprehension 更進一層樓的用法是加入條件式處理。這個使 List Comprehension 涵蓋更為廣泛的應用。
當中可以分為 2 種用法:篩選式用途(Filtering),以及辨別式用途(Distinguishing)。
篩選式用途(If)
List Comprehension 可以用於篩選列表/字典裡的項目。編程語法是:
output = [func(x) for x in input_list if cond(x)]
或者
output = {key(k): item(v) for k, v in input_dict.items() if cond(k, v)}
我們來看看以下 2 個例子:
my_list = ['士多', '啤梨', '啤梨', '蘋果', '橙']
output = [x for x in my_list if len(x) == 2]
print(output)
from pprint import pprint
my_dict = {
'啤梨': {'第一個字': '啤',
'類別': '生果'},
'士多': {'第一個字': '士',
'類別': '生果'},
'橙': {'第一個字': '橙',
'類別': '生果'},
'蘋果': {'第一個字': '蘋',
'類別': '生果'}
}
output = {k: v for k, v in my_dict.items() if v['第一個字'] == '啤'}
pprint(output, width=1)
在第 1 個例子,我們在列表選取名字有 2 個字的生果,所以輸出的列表沒有了「橙」。在第 2 個例子,我們在字典選取第一個字為「啤」的生果,所以輸出的字典只有「啤梨」和其相應的屬性(Attribute)。
留意這個用法跟 SQL 裡面的 “SELECT * WHERE COND” 和 Excel 的 =IF() 基本相同。於數據提取(Data Extraction)的角度而言,篩選式的 List Comprehension 是非常強大的工具!
辨別式用途(If-else)
除了篩選式用途,List comprehension 的條件式語法亦能做到辨別式用途,即是透過辨別 element 的特性而輸出不同結果。編程語法是:
output = [func1(x) if cond(x) else func2(x) for x in input_list]
或者
output = {(key1(k) if cond(k, v) else key2(k)): (item1(v) if cond(k, v) else item2(v)) for k, v in input_dict.items()}
這真是一個很長的語法!我們來看看實例:
my_list = ['士多', '啤梨', '啤梨', '蘋果', '橙']
output = ['一: ' + x if len(x) == 1 else '二: ' + x for x in my_list]
print(output)
在第 1 個例子,我們透過辨別式 List Comprehension 把生果的字數串連到生果名字上。第 2 個例子較為複雜:
- 以條件式在 key 註明生果的字數串連到生果名字上
- 以條件式在 value 選取生果的項目(Attribute):如果生果名字只有 1 個字,選取「類別」,否則選取「第一個字」
在數據轉型(Data Transformation)裡,辨別式的 List Comprehension 是專家。例如我們提取有關學生 JSON 數據時,想透過身高做一個簡單的分類(’高‘ if height > 170 else ‘矮'
),那麼字典的 Dict Comprehension 就會十分適合。
自訂功能
如果您需要十分複雜的數據處理,那麼 List Comprehension 仍然能夠符合您的要求!
還記得我們介紹 List Comprehension ,括號裡的是 Func(x) ?沒錯!我們可以使用自訂的功能(Custom Function)製成新的列表。(進階用家:這個類似於其他編程語言的 Lambda function)
我們來看看以下例子。
def BMI(v):
return round(v['體重'] / (v['身高'] ** 2), ndigits=2)
from pprint import pprint
my_dict = {
'碳治郎': {'身高': 1.65,
'體重': 56},
'杏壽郎': {'身高': 1.77,
'體重': 72},
'甘露寺': {'身高': 1.67,
'體重': 56},
'蝴蝶忍': {'身高': 1.51,
'體重': 37},
}
output = {k: BMI(v) for k, v in my_dict.items()}
pprint(output, width=1)
透過定義 BMI()
一功能,我們成功地計算了鬼滅每個人的 BMI,亦證明了鬼滅的人物設定頗符合身高體重。當然,除了簡單的加減乘除以外,我們亦可以透過自訂功能使用 For Loop 裡的 For Loop 計算 Fibonacci Sequence 等。
自訂功能的編程寫法亦合乎「簡化編程」的原則:我們可以分開 Loop 以及 Lambda function,使我們更容易除錯排難。
List Comprehension 的限制
當然,List Comprehension 亦不能完全取代原始 For Loop 。有時候,我們的 For Loop logic 需要更新幾個不同的變數時,即使使用了自訂功能的 List Comprehension 亦會使編程變得難以理解,甚至不能簡單地以 List Comprehension 寫出。
這類複雜的情況,我們便需要根據問題的需要,先想想有沒有辦法簡化程序。如果確實沒有簡化的餘地,那麼使用原始 For Loop 亦不失為一個好方法。最重要的,是想像自己 6 個月後再看一遍,是否能夠明白這個 List Comprehension。如果不能,那麼原始 For Loop 可能是您最好的答案。
您還想到其他 List Comprehension 的用途嗎?歡迎到「聯絡我們」告訴我們您的主意/問題!