Py 立得 — sorted function 中的 key=lambda…是什麼操作🤔?
深入解析 lambda function 的語法與運用情境
從這篇文章中,你將會學到:
lambda
的使用方法與情境- 將 function 作為 parameter 傳入並執行
- 透過
lambda
function 來建立 sorted function 的key
parameter 的變數 - 回家作業:練習設計出符合條件的
lambda
function
當初第一次看到 lambda
這個用法時,是在一個 sorted
function 的情境中,看的是一頭霧水:
basket_a = {"name": "a", "bananas": 25, "apples": 66, "mango": 16}
basket_b = {"name": "b", "bananas": 13, "apples": 74, "mango": 24}
basket_c = {"name": "c", "bananas": 35, "apples": 12, "mango": 30}
baskets = [
basket_a,
basket_b,
basket_c
]
# What does lambda mean anyway??? :(
sorted_by_banana = sorted(baskets, key=lambda basket: basket["bananas"])
for basket in sorted_by_banana:
print(basket)
# {'name': 'b', 'bananas': 13, 'apples': 74, 'mango': 24}
# {'name': 'a', 'bananas': 25, 'apples': 66, 'mango': 16}
# {'name': 'c', 'bananas': 35, 'apples': 12, 'mango': 30}
如果你跟我當時的我一樣,「完全不知道這裡發生什麼事 😖」,或者是隱隱約約知道 lambda
的用法、卻不太理解這裡的機制的話,就跟著這一篇文章,一次搞懂 lambda
與 function 參數的眉眉角角吧!
關鍵一:lambda 是 function 的偷懶寫法 😝
在 w3schools 的 lambda 說明文件 描述中,lambda function 實際上是一個 anonymous function 匿名函式。
等一下,函式 function 我知道是什麼,但「匿名函式」又是什麼意思 ?
一言以蔽之,匿名函式是一種偷懶寫法,讓你不用大費周章地從 def
開始設計函式名稱、參數、code block 與最後的回傳值,而是只關注以下兩個關鍵:
- 傳入參數
- 回傳值
我們看個簡單的例子🌰:
def sum_up(a, b):
return a+b
print(sum_up(10, 20)) # 30
上面的程式碼中,我們定義了一個簡單的 function sum_up
,回傳輸入的兩個參數的和。
如果用 lambda 來改寫上面的函數的話,就會變成這樣:
lambda_sum_up = lambda a, b: a+b
# NOTE: 這裡的 lambda_sum_up 不是 "function 名稱"
# 而是一個存有這個匿名函示記憶體位置的 "變數"!
print(lambda_sum_up(10, 20)) # 30
可以發現,透過lambda
定義的 function,就無需透過 def
語句定義函數名稱,也沒有 function 的 code block。
而在 lambda
關鍵字後,
- 冒號左邊
a, b
的部分代表的是要傳入這個匿名函式的參數 - 冒號右邊
a+b
的部分則是根據前面參數運算後,要回傳的結果
最後,我們用 lambda_sum_up
這個變數名稱來承接 lambda
建立的 function,並透過呼叫 lambda_sum_up
變數來使用 function。
我們再來看另一個例子🌰
函式:接收三個整數後,回傳其中的最大值整數的平方
然後分別用「正常函式」與「 lambda 函式」來設計
# 如果用正常函數來設計:
def square_of_the_max(a, b, c):
max_of_nums = max([a, b, c]) # 先取得三者中的最大值
return max_of_nums**2 # 回傳最大的平方
print(square_of_the_max(2, 10, 7)) # 100
# ------------------------------------------
# 如果用 lambda 建構式來設計:
lambda_square_of_the_max = lambda a, b, c: max([a, b, c])**2
print(lambda_square_of_the_max(11, 6, 20)) # 400
# ------------------------------------------
# 你甚至可以直接觸發 lambda 函式,不用再另外把它 assign 到變數上再執行!
result = (lambda a, b, c: max([a, b, c])**2)(1, 2, 3)
print(result) # 9
本質上,lambda
就是建立函式,因此原本的函式特性,都可以在 lambda 中達成。
回到一開始提到的
sum_up
的例子🌰
如果我希望函式sum_up
參數b
會有預設值 5 的話呢 🤔
# 用 def 來建立函示...
def sum_up(a, b=5):
return a+b
print(sum_up(10)) # 15,因為 b 的預設值為 5
print(sum_up(10, 20)) # 30
# lambda 來建立函示...
lambda_sum_up = lambda a,b=5: a+b
print(lambda_sum_up(10)) # 15
print(lambda_sum_up(10, 20)) # 30
小結一下,所謂的 python lambda 語法:
- 快速建立一個結構單純的 function
(是一種語法糖 syntax sugar) - 冒號左邊:定義要傳入這個 function 的參數
- 冒號右邊:定義這個 function 要回傳的結果
還記得我們剛剛說 lambda 是一個 “匿名函示 (anonymous function)” 嗎?如果你對這個說明還有點不明就理的話,執行看看下面這段程式,看看會出現什麼結果吧!
def normal_sum_up(a, b):
return a+b
lambda_sum_up = lambda a, b: a+b
print(normal_sum_up.__name__)
# normal_sum_up
print(lambda_sum_up.__name__)
# 會印出什麼呢 ?
關鍵二:function 也作為參數傳入 😮
在知道 lambda 的用法以後,第二個要認識的概念,就是 function 也能當作參數來使用!
過去我們學到,變數 (variables) 的概念就是「儲存資料」,不管是 整數 int、字串 str、布林 bool、浮點數 float、串列 list、字典 dictionary、甚至是自己定義的 class 所初始化的 instance,他們的功能就是將某個資料值儲存,並在未來透過呼叫變數名稱的方式存取。
而 函式 (function) 則是一組定義一系列操作變數行為的方式,透過 def
定義函示、傳入參數、在 code block 中設計要如何處理參數、並回傳最後的結果。
對很多初學者來說,變數與函式看起來在功能、目的上完全不同,理所當然不會將兩者視為一樣的概念;
不過,可能會出乎你意料的是,對於 Python 來說,
函式,本質上也是一種變數!
讓我們用 type
function 來觀察一下:
a_int = 1
b_list = [1, 2, 3]
c_dict = {"message": "Show me what you got!"}
def greeting():
print("ccClub the best")
print(type(a_int)) # <class 'int'>
print(type(b_list)) # <class 'list'>
print(type(c_dict)) # <class 'dict'>
print(type(greeting)) # <class 'function'>
從上面的程式碼可以觀察到,greeting 函式是一個型別為 function
的物件變數;既然是變數,那函式當然也可以作為另一個函式的參數來傳入!
當我們將函式 A 做為參數,傳入函式 B 的時候,目的是在函式 B 的流程中執行函式 A。
讓我們看個簡單的例子 🌰:
def line_1():
print("Go get it tiger!")
def line_2():
print("I can do this all day.")
def prepare_to_say_the_line(line_func):
print("Testing... Prepare to say the line...")
line_func()
print("Test done.")
prepare_to_say_the_line(line_1)
# Testing... Prepare to say the line...
# Go get it tiger!
# Test done.
prepare_to_say_the_line(line_2)
# Testing... Prepare to say the line...
# I can do this all day.
# Test done.
在 prepare_to_say_the_line
函式中,執行了傳入的函式參數 line_func
,而實際的結果則根據傳入的 line_func
函式 (line_1
, line_2
)而有所不同。
如果你傳入的參數不是函式,則會得到以下錯誤訊息 ⛔:
>> prepare_to_say_the_line("line_1")
Traceback (most recent call last):
File "/home/main.py", line 26, in <module>
prepare_to_say_the_line("line_1")
File "/home/main.py", line 18, in prepare_to_say_the_line
line_func()
TypeError: 'str' object is not callable
Type Error 說明,傳入的參數非「可調用的 callable」— 也就是非 function,不能透過 ()
的方式執行。
小結一下,關鍵二的知識點:
- 任何函式都是一種型別為
function
的物件變數,跟常見的str
,list
等等一樣。 - 函式 A 可以作為另一個函式 B 的參數來傳入,並在函式 B 中執行。
結論:用 lambda 語法,建立要傳入 sorted 的參數函式
在看完上面兩個關鍵點後,相信你已經知道開頭的情境中,sorted
內的 lambda 實際機制:
basket_a = {"name": "a", "bananas": 25, "apples": 66, "mango": 16}
basket_b = {"name": "b", "bananas": 13, "apples": 74, "mango": 24}
basket_c = {"name": "c", "bananas": 35, "apples": 12, "mango": 30}
baskets = [
basket_a,
basket_b,
basket_c
]
sorted_by_banana = sorted(baskets, key=lambda basket: basket["bananas"])
for basket in sorted_by_banana:
print(basket)
# {'name': 'b', 'bananas': 13, 'apples': 74, 'mango': 24}
# {'name': 'a', 'bananas': 25, 'apples': 66, 'mango': 16}
# {'name': 'c', 'bananas': 35, 'apples': 12, 'mango': 30}
- 透過 lambda 建立了一個 function:輸入
basket
dictionary 作為參數,回傳 basket dictionary 的bananas
這個 key 的值。 - 建立好的 function 會以參數的形式,傳入
sorted
function ,sorted
function 會將串列中的每個 element 傳入到這個 lambda function 執行;取得的結果, 將是 element 判斷大小的依據。
以我們的示範為例 🌰,我們傳入的 lambda 為
「傳入 basket dictionary,並回傳這個 dictionary key 為 bananas 的值」
而 sorted function 就是根據每個 basket 內的 bananas 值來排序,從最少排到最多,順序即是:
basket_b (bananas=15) >> basket_a (bananas=25) >> basket_c (bananas=35)
同理,如果想要改用 apples 數量作為排序依據的話,把 lambda 的回傳值調整一下就可以囉!
sorted_by_apple = sorted(baskets, key=lambda basket: basket["apples"])
for basket in sorted_by_banana:
print(basket)
# {'name': 'c', 'bananas': 35, 'apples': 12, 'mango': 30}
# {'name': 'a', 'bananas': 25, 'apples': 66, 'mango': 16}
# {'name': 'b', 'bananas': 13, 'apples': 74, 'mango': 24}
至於要在什麼情境下該使用 lambda 函式來取代一般的函式?
可以用下面兩個依據作為判斷:
- 函式本身是否單純,只關注「傳入參數」與「回傳值」的關係?
如果函式還有其他額外操作、不只是輸入與回傳的話,就不適合使用 lambda 來建立函式。 - 傳入的參數函式是否僅使用這一次?
以上述為例,如果還有其他地方也要用 bananas 作為回傳標準的話,就可以考慮把 lambda function 提取出來,而不是直接在 sorted 裏面建立,之後在把 function 名稱傳入sorted
函式:
def sorting_rule(basket):
return basket["bananas"]
# ...code...
sorted_baskets = sorted(baskets, key=sorting_rule)
# ...code...
another_sorted_baskets = sorted(another_baskets, key=sorting_rule)
回家作業:你及格了嗎?
請觀察以下的程式碼:
class Student:
name: str
chinese_score: int
math_score: int
science_score: int
society_score: int
def __init__(self, name, chinese_score, math_score, science_score, society_score) -> None:
self.name = name
self.chinese_score = chinese_score
self.math_score = math_score
self.science_score = science_score
self.chinese_score = society_score
def __repr__(self) -> str:
return self.name
ming = Student("Ming", 69, 77, 99, 48)
mei = Student("May", 88, 75, 89, 91)
mike = Student("Mike", 92, 33, 45, 60)
my_students = [
ming,
mei,
mike,
]
def check_students_pass_the_exam(students, standard_score, standard_method):
for student in students:
score = standard_method(student)
if score >= standard_score:
print(f"Congratulation! {student.name} passed the exam!")
else:
print(f"Sorry, {student.name} failed the exam...")
在上面的程式碼中,我們定義了一個學生型別 Student
,其中包含姓名 (name
)、數學成績 (math_score
)、國文成績 (chinese_score
)、自然成績 (science_score
)、社會成績 (society_score
) 等幾個科目分數的屬性。
而在 check_students_pass_the_exam
函式中,則是我們判斷學生們是否及格的函數:我會定義一個及格分數 standard_score
,以及學生計算及格分數的函式 standard_method
。
假設今天我是數學老師,我要檢查每位學生的數學成績是否超過 60 分,就可以透過以下程式碼來達成:
check_students_pass_the_exam(my_students, 60, lambda student: student.math_score)
# Congratulation! Ming passed the exam!
# Congratulation! May passed the exam!
# Sorry, Mike failed the exam...
請你寫出以下情境中,你會如何設計你的程式呢?
- 四科成績的平均分數不得低於 65 分
- 數學成績與國文成績之和不得低於 150 分
- 分數最低的學科分數不得低於 50 分
- 社會與自然至少一科不得於 80 分
我們是 ccClub 團隊,致力於讓 Python 成為大家的第二外語,希望能用淺顯易懂、循序漸進的方式,帶領新手一步步跨入程式設計的世界。
如果你喜歡這篇文章,請給我們 1~10 個掌聲👏。
如果你喜歡「Py立得」的教學系列文,請給我們 21 個以上的掌聲👏。
Facebook: ccClub Python讀書會