關於lambda function的二三事

閱讀時間約 13 分鐘
最初在Python官網的tutorial文件中看到lambda function時,雖然文件中有幾個使用範例,也在網路上看了一些相關的教學文章,但實在是看不出來有什麼非要用這玩意兒的理由。或許就如官網文件中所說的,lambda function就只是syntactic sugar而已,所以也就沒特別在意,直到在設計Game of Life的輸入介面時,因為需要用到,兜兜轉轉,費了好些功夫和時間,總算對它的用途和用法有比較完整的認識。
在Game of Life的輸入介面中,最重要也最不容易處理的部分,應該是怎麼樣可以讓人很容易輸入原始的universe樣態。既然universe是棋盤狀的方格,而每個方格,也就是cell的狀態,不是生,就是死,那最好的方式,就是可以用滑鼠點選方格來改變狀態。要想做到這樣的功能,在Python官網文件提到的module中,turtle和tkinter,是看起來比較像是含有所需功能的兩個module。
turtle主要是用來繪圖,同時也提供了處理滑鼠事件及鍵盤事件所需的工具。工具看來很齊全,不過要拿來處理universe的輸入問題,可得花費一番功夫。首先,要先畫出棋盤狀的方格。然後當使用者點選某個方格時,要透過滑鼠事件偵測滑鼠游標的位置,並計算這個位置是在哪個方格,接著改變這方格裡面所有像素的顏色。當然啦,最後還必須紀錄這個方格的狀態。唉呦!實在是太繁雜了,想想都覺得煩,不想寫。
古聖先賢總是教訓我們:做人要甘願、要勤勞,不可以偷懶!可是說實在的,要是太認命,面對工作就一頭栽進去埋頭苦幹,從不多動動腦子,想想是不是有什麼辦法可以簡化工作、提昇效率,現在哪來那麼進步的科技?不說別的,先前寫「核心計算」部分,在判斷cell是不是位於universe邊界的時候,要不是看著一堆if不順眼,同時也覺得,要考慮那麼多不同的邊界狀況,實在是有夠麻煩的,又怎會去想辦法設計出更乾淨俐落又有效的方法來?所以啊,要多想想是不是有那種可以偷懶,但卻有效的辦法。
既然不想用turtle做,那用tkinter行不行?tkinter主要是用來設計GUI,所以像是按鈕、標籤、視窗、捲軸、選單等各種常見的元件,一應俱全。不過挺討厭的是,有些元件在不同的程式語言中,有不同的名稱,所以明明知道元件的長相,還得費一番功夫找出對應的名稱。唉!就不能大家都叫一樣的名稱嗎?鐵鎚就長那樣,你叫它榔頭,它還是長那樣;checkbox就長那樣,你叫它checkbutton,它還是長那樣,那幹嘛弄個不同的名稱作弄人?很煩咧!不過煩歸煩,還是要想想有沒有哪個元件合用。
在tkinter中,每個元件的長相、功能都是固定的,能夠調整的東西就那些,所以現在主要的問題是:universe是棋盤狀的方格,用滑鼠點選方格時,方格的狀態以及顏色會改變。有哪些元件可以做出這樣的效果?先前在考慮用turtle做時,是把universe看成是一大塊,然後在裡頭畫出小方格。很顯然的,對於tkinter的元件來說,這樣子的做法行不通,得想想是不是有其他辦法。
既然在一大塊裡頭畫出小方格的方式行不通,那用一堆小方格組成一大塊行不行?在寫「核心計算」部分時,就曾經想過類似的問題。那時候在想:是要把universe看成是點陣圖,圖裡頭的pixel就代表cell,還是要把所有代表cell的方格看成是一個一個獨立的物件,由這些獨立的物件拼湊組成universe?這兩種方式都可以達到目的,差別只在於程式好不好寫,還有處理的效率。後來之所以採用點陣圖的方式,是覺得這樣會比較好寫。那現在如果把方格看成是物件,透過滑鼠的點選可以改變顏色,還能同時處理資料,有沒有哪個tkinter的元件具備這樣的功能?嘿嘿嘿!那個網路上到處都有的「按鈕」,不就是個這樣子的存在嗎?!
tkinter的按鈕元件語法中,當按鈕被按下時,要觸發的事件是透過command這個parameter來指定,寫法是:command = callback_function,也就是說,當按鈕按下時,會去執行callback_function。這看起來挺單純的,網路上許多教學文章都能看到說明。不過啊不過,網路上絕大多數的教學文章,都是一個按鈕配上一個callback_function,所以顯得很單純。但是現在是要用一大堆的按鈕來組成universe,這可不能聽從古聖先賢做人要甘願、要勤勞、不可以偷懶的教訓,真的埋頭苦幹,一個一個按鈕、一個一個callback_function的寫一大堆。那要怎麼寫?當然是用迴圈加上list來寫囉!而lambda function也就在這兒展現它的存在感。
雖然lambda function在這裡展現了它的存在感,不過這過程挺不乾脆的,遮遮掩掩、躲躲藏藏,頗費了一番功夫和時間才撥雲見日,把它給看個通透。
一開始還不知道要用lambda function,所以程式寫成這樣:
def change_state(btn):        
  btn['bg'] = 'red' if btn['bg']=='white' else 'white'
for i in range(10):
  btn[i] = tk.Button(root_window, bg='white',                                       command=change_state(btn[i]))
這段程式的目的,是產生10個白色的按鈕,當滑鼠點選按鈕時,那個被點選按鈕的顏色,就會由白色變為紅色,或由紅色變為白色。只是這樣子的寫法是錯的,在change_state中,會出現錯誤訊息:
TypeError: 'int' object is not subscriptable
想想也對,這btn[i]都還沒生出來,就把它當argument傳給change_state,如果這樣也可以,那就太神奇了。既然如此,那就等btn[i]生出來後,再來處理command,程式的迴圈部分就改成:
for i in range(10):
  btn[i] = tk.Button(root_window, bg='white')
  btn[i].configure(command=change_state(btn[i]))
改成這樣之後,錯誤訊息是消除了,不過生出來的所有按鈕,都是紅色的,而且不管怎麼點,顏色都不會變。
這到底是怎麼回事呢?實在是摸不著頭緒,後來想起曾在教學文章中有提到,可以用lambda function把callback_function的程式碼直接寫在command之後,而不用另外寫在def中,於是就把configure那行程式改成:
btn[i].configure(command=lambda: change_state(btn[i]))
天不從人願,這樣寫也不行。按鈕是生出來了,而且點選時顏色也會切換,可是不管點選哪個按鈕,切換顏色的,永遠是最後那個按鈕。
會不會是因為lambda function裡頭,呼叫change_state時,是用for迴圈的index來直接指定要改變狀態的是哪個按鈕,所以造成這樣的結果?改用parameter的寫法不知道行不行得通?試試看就知道!把lambda function改成這樣:
lambda n: change_state(btn[n])
結果不改還至少有個按鈕顏色會切換,改了之後,所有的按鈕都是白色的,不管怎麼按,就是不會變成紅色的。
唉!沒輒了,上網找答案。
原來,要這樣寫才對:
lambda n=i: change_state(btn[n])
這什麼語法啊,n=i?還有啊,那幾個錯誤的寫法,到底問題在哪?這樣子的疑問想要上網找解答,還真是考驗搜尋的功力。搜尋看似簡單,但要是不知道要用什麼關鍵字去找,那還真像是在荒郊野外找路一樣,充滿了天地茫茫,何方是歸處的感覺。
找了很久,試了很多關鍵字,每隔幾天想到新的方向、新的關鍵字就找一找,總算在stack overflow的討論串裡頭,看到了個比較清楚的解釋。好玩的是,那個討論串是在討論lambda function到底有什麼用,而不是在討論tkinter的按鈕寫法。難怪先前用tkinter相關的關鍵字搜尋,會找不到比較清楚完整的解釋,方向不對啊!
正確的寫法有了,那先前錯誤的寫法,到底錯在哪了?簡單來說,問題就出在對lambda function的運作方式不夠瞭解。
程式寫成lambda: change_state(btn[i])時,因為i是for迴圈的index,是在lambda function外面,是global variable,所以只有在lambda function被呼叫時,才會去存取i。在迴圈裡頭的程式碼,雖然造出了按鈕,但也就僅止於這樣,沒有去執行其他的什麼東西。而lambda function的部分,也就只是定義而已,要等到按鈕被按下,才會去執行。迴圈執行完建造按鈕的工作後,i的值是9,所以當按鈕被按下時,呼叫lambda function執行的,就是change_state(btn[9])。難怪不管按哪個按鈕,都是最後一個改變顏色。如果把lambda function改成lambda: change_state(btn[i-1]),那不管按哪個按鈕,都是倒數第二個改變顏色,可以進一步驗證這個解釋。
那後來把程式改成lambda n: change_state(btn[n])時,問題又出在哪?原來,因為n是lambda function的parameter,所以當按下按鈕時,n還是n,它一直就是n,不會變成argument。既然如此,不管按鈕怎麼按,根本就沒有任何argument傳給change_state,它只好把你的呼叫動作當空氣,已讀不回。
因為在定義lambda function時,並不會去存取global variable,所以在建造按鈕時,必須使用local variable來讓傳入change_state的按鈕,與當時迴圈index所指定的,是一致的。就因為是這樣,所以才會寫成lambda n=i: change_state(btn[n])。
呼!總算搞懂是怎麼回事了!可是,那個n=i又是怎麼回事?網路上找到的討論,都是直接就這麼寫、這麼用,沒有解釋。這個問題放在心中好一陣子,直到有天晚上心血來潮,想說在官網的文件找找看,說不定可以找到。
哈!還真的找到了!以lambda function為目標來找,在官網文件中的一些說明,不但解答了這個疑惑,連先前花了好多時間才在網路上找到解答的疑惑,都有很清楚的說明。這些文件包括:
  • Programming FAQ: Why do lambdas defined in a loop with different values all return the same result?
  • The Python Turorial: 4.8.6. Lambda Expressions
  • The Python Language Reference: 6.14. Lambdas
n=i這個語法的說明,就躲在第三個文件裡頭,而且還偽裝得挺好的,乍看之下沒察覺,直到後來才發現。怎麼說呢?文件裡頭是這麼寫的:
lambda_expr ::= "lambda" [parameter_list] ":" expression
然後在最後一段裡頭提到,那個parameter lists的語法,就參考8.7. Function definitions裡頭的說明。看到那段話,突然間醒悟過來:n=i不就是呼叫function時,keyward argument的寫法嗎?都說是lambda “function”了,在呼叫時,argument的寫法,不就該和function一樣?!真是的,怎麼就一直沒想到咧?!
其實那幾份文件先前都看過,只是那時就只是為了要知道Python有哪些東西而看,並不是為了尋求疑問的解答而看,所以就有如走馬看花,有看沒有到。為了解答心中的疑惑而看文件,就好像打開了主動雷達,原本被動接收,看來平凡無奇不是那麼重要的內容,就變成字字珠璣,充滿了深意。想想這尋覓瞭解lambda function的過程,還真是「衆裏尋他千百度,驀然回首……」像極了愛情啊~~~
後記:準備結束這篇lambda function的隨筆時,想到還有個疑問還沒解決,就是寫成btn[i].configure(command=change_state(btn[i]))時,為什麼生出來的所有按鈕,都是紅色的,而且不管怎麼點,顏色都不會變?上網查了一下,原因是因為在command=change_state(btn[i])中,change_state(btn[i])是keyward argument,所以實際上指定給command的,是執行change_state(btn[i])之後的傳回值。在建造按鈕時,會執行一次change_state。而因為change_state並沒有任何實際上的傳回值,它傳回的是None,所以最後得到的結果是command=None。這也就是為什麼所有的按鈕都是紅色的,而且點選時沒任何反應的原因。
即將進入廣告,捲動後可繼續閱讀
為什麼會看到廣告
15會員
129內容數
寫點東西自娛娛人
留言0
查看全部
發表第一個留言支持創作者!
ysf的沙龍 的其他內容
唉!這user可真難伺候啊~~~
Game of Life的輸出結果,也就是每代演化後universe的長相,程式要怎麼寫呢?現在有個二維list,裡頭放的是一堆0和1,要怎麼樣用西洋棋盤式的方式來顯示呢?這看來勢必得用到別人寫好的module來做,才能省時、省力又漂亮。第一個想到的,當然就是matplotlib這個科學繪圖用的mo
如果「李大仁」長得像「武大郎」,而「程又青」長得像「東施」,這戲,還會有人看嗎?
Game of Life的「核心計算」部分寫好了,短短的沒幾行,畢竟也就那麼幾條判斷規則而已,沒什麼太複雜的東東要處理。說是寫好了,但到底能不能跑、跑出來的結果對不對,那可還是在未定之天哩。
在學習新事物的時候,如果能善用類比、聯想等小技巧,往往能事半功倍,但也是有可能會碰壁的。
適當的休息可以把陷進泥淖而不自知的心思給拉出來,從不同的角度和觀點來看問題,這樣說不定會發現另外不同的解決問題的方法。
唉!這user可真難伺候啊~~~
Game of Life的輸出結果,也就是每代演化後universe的長相,程式要怎麼寫呢?現在有個二維list,裡頭放的是一堆0和1,要怎麼樣用西洋棋盤式的方式來顯示呢?這看來勢必得用到別人寫好的module來做,才能省時、省力又漂亮。第一個想到的,當然就是matplotlib這個科學繪圖用的mo
如果「李大仁」長得像「武大郎」,而「程又青」長得像「東施」,這戲,還會有人看嗎?
Game of Life的「核心計算」部分寫好了,短短的沒幾行,畢竟也就那麼幾條判斷規則而已,沒什麼太複雜的東東要處理。說是寫好了,但到底能不能跑、跑出來的結果對不對,那可還是在未定之天哩。
在學習新事物的時候,如果能善用類比、聯想等小技巧,往往能事半功倍,但也是有可能會碰壁的。
適當的休息可以把陷進泥淖而不自知的心思給拉出來,從不同的角度和觀點來看問題,這樣說不定會發現另外不同的解決問題的方法。
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
最近有一些新的覺察,邁入大學的生涯後,更加的明白,有很多的事情需要自己去體會、感受。才能找出屬於自己的最佳解。 大學就是一個小型的社會,多少時間是社交,又多少時間是留給自己,是一個流動的狀態,卻是大部分會給我帶來情緒波動的主因。 最近感悟到,當我在面對與自己完全不同的人時學到了如果對方沒有主動問
在棋盤的每一角落,棄子不僅是一種策略,更是一門藝術。
Thumbnail
不純粹的閱讀,你必然得到純粹的結論,例如看到討厭的同學發臉文,心裡開始品頭論足,最後得出他失戀失業的無能結論。嗯,或許討厭的同學只是單純實驗做失敗而已。 這年代了解不純粹的純粹很重要,還記得2024總統大選,有許多工程師小草放大自己的專業能力,覺得別人都很笨,用極大的意義解讀其他政治人物的動機?
Thumbnail
畢業季,離情依依,怕感情變疏遠!送別禮物特別能延續感情,但要送什麼好呢?讓友情歷久彌堅,分享好朋友送禮攻略!好書推薦「長輩沒教,但你一定要懂的81種送禮大學問」!
自從七月底陷入無止盡的鬱期之後 現在好像是進入躁期 毛毛躁躁的,不管是頭髮還是心理狀態(笑) 回到了無法看完一本書,一部影片的狀態 連文章都是打開了網頁,敲敲敲然後又再全部backspace. 有人問我說我的躁期會持續多久 因為長久以來我比較專注在鬱期的發展 所以沒有特別另外又去記錄自己躁期的階
Thumbnail
Yoga Retreat @ Greece  I do not know who need to hear this, but here it goes When you are blessed with a responsibility as a leader, teacher, coach,
Thumbnail
本文是2009/7/31號所寫,更新到方格子上。 有人說『成熟是一種智慧的成長』。 有人說『成熟是一個重生,靈性上的誕生,你煥然一新,又是個小孩子。』 有人說『成熟是會去改變自己。』 最後一句話我覺得很有道理,成熟的人會去改變自己而不是去努力改變環境。這些年的教訓
Thumbnail
對於簡報跟創業,曾經都以為跟自己很遙遠。也因此,能夠在這幾年,許多機緣引發的投入、際遇以及累積跟成長下,擔任首個華人新創簡報品牌、寶渥公司,舉辦本年度新創簡報競賽、PitchCamp2021的評審,不只是一個榮譽、也是一個肯定。這些機緣,絕大多數來自於BNI跟商戰經理人讀書會。
Thumbnail
Amazon製作了Invincible的動畫,第一季於前陣子上架。大概成果不錯,已經預訂了第二季跟第三季。 Invincible的漫畫跟動畫有甚麼不同,你可能想知道。
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
最近有一些新的覺察,邁入大學的生涯後,更加的明白,有很多的事情需要自己去體會、感受。才能找出屬於自己的最佳解。 大學就是一個小型的社會,多少時間是社交,又多少時間是留給自己,是一個流動的狀態,卻是大部分會給我帶來情緒波動的主因。 最近感悟到,當我在面對與自己完全不同的人時學到了如果對方沒有主動問
在棋盤的每一角落,棄子不僅是一種策略,更是一門藝術。
Thumbnail
不純粹的閱讀,你必然得到純粹的結論,例如看到討厭的同學發臉文,心裡開始品頭論足,最後得出他失戀失業的無能結論。嗯,或許討厭的同學只是單純實驗做失敗而已。 這年代了解不純粹的純粹很重要,還記得2024總統大選,有許多工程師小草放大自己的專業能力,覺得別人都很笨,用極大的意義解讀其他政治人物的動機?
Thumbnail
畢業季,離情依依,怕感情變疏遠!送別禮物特別能延續感情,但要送什麼好呢?讓友情歷久彌堅,分享好朋友送禮攻略!好書推薦「長輩沒教,但你一定要懂的81種送禮大學問」!
自從七月底陷入無止盡的鬱期之後 現在好像是進入躁期 毛毛躁躁的,不管是頭髮還是心理狀態(笑) 回到了無法看完一本書,一部影片的狀態 連文章都是打開了網頁,敲敲敲然後又再全部backspace. 有人問我說我的躁期會持續多久 因為長久以來我比較專注在鬱期的發展 所以沒有特別另外又去記錄自己躁期的階
Thumbnail
Yoga Retreat @ Greece  I do not know who need to hear this, but here it goes When you are blessed with a responsibility as a leader, teacher, coach,
Thumbnail
本文是2009/7/31號所寫,更新到方格子上。 有人說『成熟是一種智慧的成長』。 有人說『成熟是一個重生,靈性上的誕生,你煥然一新,又是個小孩子。』 有人說『成熟是會去改變自己。』 最後一句話我覺得很有道理,成熟的人會去改變自己而不是去努力改變環境。這些年的教訓
Thumbnail
對於簡報跟創業,曾經都以為跟自己很遙遠。也因此,能夠在這幾年,許多機緣引發的投入、際遇以及累積跟成長下,擔任首個華人新創簡報品牌、寶渥公司,舉辦本年度新創簡報競賽、PitchCamp2021的評審,不只是一個榮譽、也是一個肯定。這些機緣,絕大多數來自於BNI跟商戰經理人讀書會。
Thumbnail
Amazon製作了Invincible的動畫,第一季於前陣子上架。大概成果不錯,已經預訂了第二季跟第三季。 Invincible的漫畫跟動畫有甚麼不同,你可能想知道。