本文出自“Python為什麼”系列,請檢視全部文章
在這篇文章裡,我們會聊一聊為什麼 Python 決定不支援 switch 語句。
為什麼想要聊這個話題呢?
主要是因為 switch 在其它語言中太常見了,而 Python 卻不支援,這樣的獨特性本身就值得關注,而回答這個問題,也能更加看清 Python 在程式設計上的理念,了解 Python 在文法設計中的決策過程。
本文除了會詳細分析 PEP-275 和 PEP-3103,還會介紹到 Python 最新的發展動态(PEP-622),即可能要引入的模式比對(pattern matching)文法,相信這個話題會開闊大家的眼界,進而對 switch 文法有更為全面的認識。
在開始正題之前,我們需要先聊聊 switch 是什麼?
有些同學可能會第一時間想到它……
喂喂,麻煩收收心,别總想着遊戲啦,我們要說的是程式設計語言中的 switch 語句。
一般而言,switch 的文法格式如下:
使用流程圖來表示,大概是這樣的:
它的用法不難了解:switch 語句的值滿足哪一個 case 情況,就會執行對應的代碼塊,執行時遇到 break 就跳出,否則就繼續執行下一個 case 分支;一般會在最後放一個 default 分支,作為兜底。
大多數語言都提供了 switch 語句或者極其相似的東西,例如,在 C/C++/Java /Go 等靜态語言中,它們都支援 switch-case 結構;在 Ruby 中有類似的 case-when 結構,在 Shell 語言中,有相似的 case-in 結構,在 Perl 中,有 switch-case-else……
switch 語句的好處是支援“單條件多分支”的選擇結構,相比 if-else 的二分選擇結構,在某些時候會更為簡潔清晰。
但是,在 Python 中,我們看不到 switch-case 或者相近的文法結構,這是為什麼呢?
官方文檔中有一篇 FAQ 包含了這個問題:Why isn’t there a switch or case statement in Python?
FAQ 即 Frequently Asked Questions 的縮寫,表示常見問題,官方列了 27 個常見問題,完整清單在此:https://mp.weixin.qq.com/s/zabIvt4dfu_rf7SmGZXqXg
該文檔給出了幾個建議,告訴了我們幾個 switch/case 的替代方案:
使用 if-elif-else 條件判斷語句
使用字典,将 case 值與調用的函數映射起來
使用内置 getattr() 檢索特定的對象調用方法
曾有人提出過一些提案(即 PEP-275 和 PEP-3103),想給 Python 引入 switch 文法,然而,對于“是否以及如何進行靶場測試”,大家沒有達成一緻的共識。
靶場測試,即 range test,指的是對武器彈藥的技術性能作各種測試驗證,與藥物的臨床試驗一樣,都是在最終産品傳遞前的一項關鍵性測試。
官方文檔對于“為什麼 Python 不引入 switch”的解釋,實際上來源于 Python 之父 Guido van Rossum 在 PEP-3103 中的意見:
出處:https://www.python.org/dev/peps/pep-3103/
A quick poll during my keynote presentation at PyCon 2007 shows this proposal has no popular support. I therefore reject it. 我在 PyCon 2007 的主題演講中做了一個快速的民意調查,結果表明這個提案沒有得到廣泛的支援。是以,我拒絕了它。
簡而言之,PEP 提案有了,文法實作也有了雛形,但是核心開發者們似乎沒有達成一緻意見,最終導緻提案流産了。
PEP-3103 是在 2006 年提出的,PEP-275 則是在 2001 年提出的,它們的共同之處是提出了引入 switch 語句的某種必要性、分析了好幾種備選的實作方案,然而,結局是都被拒絕了。
出處:https://www.python.org/dev/peps/pep-0275/
那麼,我們就先來回顧一下核心開發者們都做出了哪些讨論,看一看如果 Python 要實作 switch 結構,會是怎麼樣子的?(PS:PEP 裡還涉及其它内容,本文隻摘取與 switch 直接相關的部分)
PEP-275 提出的文法結構如下:
其中 else 分支是可選的,如果沒有它,并且前面的分支都不滿足的話,就什麼也不做。另外 case 值 constant 支援不同類型,因為 expr 表達式的類型是動态的。
PEP-275 還提出讓 switch 不支援掉落(fall-through)行為,即每個 case 分支互相獨立而完整,不用像 C 語言那樣需要寫 break。
該 PEP 還列舉了一些其它的 issue:
重用現有關鍵字,不引入“switch”和“case”
使用新的關鍵字,避免與 C 的 switch 概念混淆
支援單分支多值選擇(例如:case 'a', 'b', 'c': ...)
還有建議支援範圍取值判斷(例如:case 10..14: ...)
除了首選方案,該 PEP 還記錄了幾種風格各異的文法方案:
PEP-275 記錄下了不少重要的思路和問題,為 PEP-3103 的出現做了很好的鋪墊。
那麼,我們再來看看由 Guido 編寫的 PEP-3103 說了些什麼吧。
它首先認可了 PEP-275 中的兩個基礎設定,例如,實作“隐式的 break”,不讓 case 分支出現 fall-through 這種轉移控制權的情況(其它語言似乎都要求顯式地寫 break);else 分支是可選的,複用 else 關鍵字,而不用引入“default”。
對于 PEP-275 提倡的那種風格,Guido 比較認可,但也認為它的問題是縮進層次太多,是以建議減少代碼分支縮進的空格數,例如本來縮進 4 空格,改為縮進 2 空格。
PEP-3103 還列舉了另外三種實作方案,分析了它們的差異以及問題,具體内容從略,這裡隻給大家看看它們的風格:
在基礎文法之外,Guido 花了很多篇幅來讨論擴充文法(Extended Syntax),即在一個 case 分支中實作比對多個值的複雜情況:
他重點考慮到的問題包括:switch 中表達式的結果是元組或可疊代對象的情況、case 的值被看成元組解包的情況、在 case 分支作“*”星号操作……
接着,Guido 又用了非常非常多的篇幅來分析該如何實作 switch,其中讨論到的主要思路有:
使用等價的 if-elif 鍊來定義 switch 語句(可能會做些優化)
同上,另外所有表達式都必須是可哈希的(hashable)
看作是預先計算的字典的分派(dispatch)
PEP 中這部分的内容非常多,因為在每個思路上,Guido 還考慮到了好幾種實作路徑,這導緻了他在複雜分析後的結論是:It is too early to decide( 現在做決定為時尚早)。
閱讀完 PEP-3103 後,我總體的感覺是:Guido 的思路非常發散、層次豐富,但是,缺少了他在面對其它問題時那“快刀斬亂麻”式的洞察力。
也就是說,在諸多的可能性方案中,他力求面面俱到,最終無法說服自己做出一個獨裁的決定。阻力主要來自于他自己,而不是其他人。
不過,之是以會出現這種情況,也許跟他的預設立場有關:他似乎認為“Python is fine without a switch statement”,是以盡管寫了很長的 PEP,但隻是在把問題複雜化,把議題擱置起來。
最後,他在 PyCon 上做了一個小範圍調查,借此“名正言順”地拒絕了自己發起的 PEP,試圖堵住衆人的悠悠之口……
歸結起來,之是以 Python 沒有 switch 語句,原因有:switch 的實作細節/功能點未經敲定、沒有 switch 也挺好的、有其它不錯的方法替代 switch、Guido 的小任性……
但是,我們還是要追問一句:未來會有 switch 語句麼?或者類似的多分支選擇結構?
為什麼要有此一問呢?原因是有太多語言自帶 switch 語句,而且也有很多人嘗試編寫提供 switch 功能的庫(我記得在 PyCoder's Weekly 裡曾見到過兩次)。
我(Python貓)本人自始至終并不喜歡 switch,幾乎可以肯定地說,Python 未來也不會有 switch,但是,它很可能會引入一個類似于 switch 且更為複雜的文法結構!
2020 年 6 月,PEP-622 被提出了,它建議引入在 Scala、Erlang 和 Rust 等語言中的模式比對文法(pattern matching)。
截至 2020 年 10 月,該 PEP 已被分解成另外三個 PEP(634-636),目前都處于草案階段。考慮到核心開發者們的參與情況以及話題讨論的情況,這些提案極有可能會在未來版本(比如正在開發中的 3.10)中實作。
以一個求平均數的函數為例,模式比對文法可以實作成這樣:
match-case 結構神似于 switch-case 結構,然而它基于模式(pattern)而非表達式(expression),是以有更多待考慮的細節問題,也有更為廣闊的應用空間。
對此話題感興趣的讀者,建議去查閱這幾個新的 PEP。
最後,讓我們回到标題中的問題:Python 為什麼不支援 switch 語句?
官方文檔的 FAQ 對此問題有一個解答,告訴我們有幾個不錯的替代寫法,同時也留下了一條線索:曾有 PEP 提議引入 switch,隻是沒有成功實作。
沿着這條線索,本文拆解了 PEP-275 和 PEP-3103 這兩篇文檔,帶大家看到了 Python 社群裡提出過的風格各異的 switch 方案,以及諸多的懸而未決的問題。
最後,我們還關注到了最新的 PEP-622 的動态,看起來 switch 的“孿生兄弟” match 文法有望引入到 Python 中!switch 話題的讨論似乎要終止了,但是另一個更大的話題正在進行中!
本文屬于“Python為什麼”系列(Python貓出品),該系列主要關注 Python 的文法、設計和發展等話題,以一個個“為什麼”式的問題為切入點,試着展現 Python 的迷人魅力。所有文章将會歸檔在 Github 上,歡迎大家給顆小星星,項目位址:https://github.com/chinesehuazhou/python-whydo