當Python 3.10推出後,為人關注的新特性之一是結構化模式比對(Structural Pattern Matching),而這個特性最早是在PEP622中提出,後來整理為PEP634,到了PEP635與636當中,分別討論動機,以及提供文件入門;然後,有許多文件就開始嚷嚷著:「Python的switch語法終於出現了」!

Python不需要switch?

當然,Python結構化模式比對使用的是關鍵字match,而不是switch,然而,這其實反映了一件事實:其他語言幾乎都有switch之類的語句,因為「別人都有,就你沒有」的關係,使得Python沒有switch這件事,相對而言就像個異端。

因為其他語言幾乎都有,這就使得從其他語言來到Python的使用者,理所當然地,會想要有switch的方案,在Python社群中,也確實曾有提案與討論,而且歷史甚為悠久——早在2001年就提出了PEP275,後來在2006年又有了PEP3103,然而最終它們都被否決了。

基本上,否決的原因在於,在社群各式各樣的提案中,沒有一個實現可適切地融入Python既有的語法與風格。事實上,Python之父Guido van Rossum在PEP3103也分析許多情境與方案,以及它們面對的問題,但都覺得過於複雜,若不使用switch的方案還比較單純,於是,其在PEP3103的最後結論就是「現在定案還太早了(It is too early to decide)」。

Guido後來在PyCon 2007時做了個快速調查,就投票結果來聲稱PEP3103不受歡迎,合理正當地予以否決了。只不過,「為什麼沒有switch?」這問題仍然被問到爛掉了。甚至,在Python官方的〈Design and History FAQ〉直接列出條目來回答這陳年問題,就結論而言,使用if/elif/else或dict完全可以應付需求,然而,乍看之下,很多人應該也會覺得這就是在說「Python就是不需要switch」!

不要使用switch?

「講這麼多,最後還不是加入了switch?」是有些人面對PEP634的論調,這結果論看似大快人心,但社群中也有人不怎麼買單,像是Ben Hoyt寫的文章就論述了正反面的效益,在反面批判上雖不留情,然而,在是否使用新特性這方面,是有些可以採納、參考的觀點。

Python官方FAQ談到,可以使用if/elif/else來滿足switch的需求,若是覺得寫來冗長,可以改用dict。

因為,在Python建立dict很容易,而且,函式在Python中是一級值,可以將動作封裝為函式,作為dict映射的對象,如此一來,就能滿足值與動作的映射需求。

如果你是屬於「要求有switch」的一派,對於這種建議可能不以為然,那麼,來看看本來就有switch的語言吧!搜尋「don't use switch」,你可能會訝異這些語言在不少文件中,竟然建議不要使用switch。

舉例來說,O'Reilly的《JavaScript應用程式開發實務》,其JavaScript風格指南就建議不要使用switch,可以採用物件實字,透過特性名稱查詢相對應的函式更為靈活,作者也提出了幾個情境,在想要寫下switch處理這些情境前,多思考一下是否有其他可行的設計。

你可能會說「那是JavaScript啊!」而在O'Reilly的《21世紀C語言》第二版中的〈教科書過分強調的進階語法〉也談到,switch的break與default可能帶來微妙的臭蟲,若想避免,就不要使用switch,其中也說到替代方案之一,就是使用一系列的if與else。

你可以找到更多類似的文件,有些switch可以進一步地改用子型態多型來取代,有些則談到switch會讓階層失控、流程複雜化而難以除錯等。看到這裡,突然間你會覺得,使用switch反而是一種罪過了。

switch或match?

在具有switch的語言中,完全地拒用switch又有點矯枉過正了,畢竟就一名語言初學者而言,若要將特定值對應至某些動作或(傳回)值,switch是個簡單、便利、學習曲線低的工具,如果程式邏輯極為簡單,原始碼在閱讀上也不複雜而直覺,沒道理不使用;然而,請切記不要濫用,若各個case的邏輯或層次變得複雜而難以閱讀時,就應考慮其他設計方式的可行性。

當然,Python到3.10之前就是沒有switch,也沒因此而有什麼功能寫不出來,畢竟if/elif/else可以涵蓋switch的作用,使用dict也可以處理掉很多事,甚至有人覺得,沒有switch的Python,反而讓他累積許多映射的實務經驗,成為更好的開發者。

確實地,switch的本質上就是映射,「將值映射至值或動作」,而這就是字典之類的結構可以作為替代方案的原因,某些程度上,Python生態圈沒有switch成為一種良性約束,也讓程式碼有機會獲得更好的設計。

Python 3.10的match語法,從名稱上看來就代表映射,不過,請別忘了PEP634的全名是結構化模式比對,而match是將具有特定結構的資料映射至值或動作,其結構就包含了型態、資料成員的名稱、順序、值等。

這也就是為什麼match可以搭配的,都是些能提供結構資訊的對象,像是:list、dict、datatclass、namedtuple,以及具有__match_args__定義名稱的類別實例,否則,就要明確地以關鍵字引數指出結構資訊。

也就是說,match語法非常強大而複雜,而這份複雜,也只是剛好涵蓋了將值映射至值或動作的單純任務。

如果你只是將match像switch那樣使用,等於是解除了方才提及的良性約束,那麼,相對地就要思考:「有因此而獲得更好的設計嗎?」或者只是因為一時的方便使用了match,從而之後因為更複雜的情況,不斷使用了match更複雜的語法,讓match變得更複雜難懂,甚至後續必須得面對match更高的學習曲線,以及維護負擔呢?

如果只是將將值映射至值或動作的單純任務,Python過去幾十年來已經累積許多經驗了,這類需求在Python 3.10以後,不見得就要改用match來滿足;相對地,match的應用場合,就是在那些真的要有特定結構的資料映射至值或動作的場合。

match根本就不是switch

那麼,哪些場合才適用結構化模式比對呢?接觸過函數式語言的開發者,可能早就知道答案了:函數式中的模式比對,才是Python結構化模式比對的模仿對象。也因此,函數式中的模式比對應用場合,可以作為借鏡的對象。

這時,就不得不提一下Java未來的發展路線,這並不算離題,因為Java也正朝著結構化模式比對的類似道路前進,先前三篇專欄〈不只是語法糖的記錄類別〉、〈揭露型態邊界的彌封類別〉、〈模式比對與多型〉中的相關討論與思考,對Python結構化模式比對的應用會有助益。

當然,Python並不是要提倡函數式設計了,只是有些場合會適用結構化模式比對,應用時的思考出發點並非研究match本身,是「為什麼Python始終不加入switch?」記得,那是一種良性的約束,而在面對match新功能時,請謹記:「match根本就不是switch」,它真正處理的是「將特定結構的資料映射至值或動作」!

專欄作者


熱門新聞

Advertisement