Python 為什麼不設計 do-while 迴圈結構?
在某些程式語言中,例如 C/C++、C#、PHP、Java、JavaScript 等等,do-while 是一種基本的迴圈結構。
它的核心語義是:先執行一遍 迴圈體程式碼 ,然後執行一遍 條件語句 ,若條件語句判斷為真,則繼續執行迴圈體程式碼,並再次執行條件語句;直到條件語句判斷為假,則跳出迴圈結構。
流程圖如下(Java 示例):
// 列印小於 20 的數字 public class Test { public static void main(String[] args){ int x = 10; do { System.out.print("value of x : " + x ); x++; System.out.print("\n"); } while(x < 20); } }
Python 並不支援 do-while 結構,“do”並不是一個有效的關鍵字。
那麼,為什麼 Python 不提供這種語法結構呢,這種現狀的背後有何種設計考量因素呢?
在回答這個問題之前,讓我們再仔細思考一下 do-while 語法可以解決什麼問題,看看使用這種結構能帶來什麼好處?
最顯而易見的好處是:do-while 語法保證了會先執行一遍迴圈體程式碼。
它的使用場景也許不多,但是,跟普通的 while 迴圈或者 for 迴圈語法的“ 條件前置 ”思想不同,它體現的是一種“ 條件後置 ”的程式設計邏輯,也是一種控制迴圈的常見方式。
它們的關係似乎有點像 C/C++ 這些語言中的 i++
與 ++i
操作的區別,在某些特殊場合中,也許會更為高效。
除了這一特點,這種結構最大的應用場景其實是在 C/C++ 中特殊的 do {...} while (0)
用法。這在很多開源專案的原始碼中都能找到蹤跡,例如 Linux、Redis 以及 CPython 直譯器,等等。
這裡面的數字 0 表示布林值 False,意味著迴圈只會執行一遍,然後就跳出。
這樣的寫法是不是很詭異?所謂“迴圈”,一般就意味著程式體會被反覆執行多次,但是, do {...} while (0)
卻偏偏只需要它執行一遍,這初看起來是有點多餘啊。
這種寫法主要用在巨集函式的定義中,可以解決巨集程式碼塊的編譯問題,使程式碼按照我們的意圖而合理分塊。
另外, do {...} while (0)
結合 break 使用,還可以實現很優雅的跳轉控制效果。
在下面的示例中,步驟 1、4 和 5 要求必須執行,而步驟 2 取決於步驟 1 的執行結果,步驟 3 則取決於步驟 2 的執行結果。
do { // 執行步驟 1 if (條件1失敗) { break; } // 執行步驟 2 if (條件2失敗) { break; } // 執行步驟 3 if (條件3失敗) { break; } } while(0); // 執行步驟 4 // 執行步驟 5
在這種場景中,我們確實只需要按照順序執行一遍。do-while 結構很清晰,避免造成多層條件巢狀或者設定諸多額外標記的局面。
最後還有一點,在彙編層面,do-while 比 while 更接近組合語言的邏輯,可以節省使用指令,在過去的低記憶體時代,算得上是一種優化寫法。
分析完 do-while 的好處後,讓我們回到主題:Python 為什麼不需要設計 do-while 迴圈語法呢?
首先,Python 離底層應用程式設計太遠了,就不用考慮彙編指令的優化了,同時,它也不涉及巨集的使用。
至於“條件前置”和“條件後置”的區別,其實並沒有太大影響,而且,由於 Python 使用簡潔優雅的縮排加冒號語法來劃分程式碼塊,導致直譯過來的 do-while 語法看起來會很怪異(注意,直譯的 while 的條件後沒有其它內容):
do: pass while False
想要引入新的語法特性,必然要遵守既定的風格習慣。其它語言的 do-while 結構直譯成 Python 的話,肯定不合適。
事實上,在 2003 年時,有一個 PEP 提議給 Python 加上 do-while 語法支援:
PEP-315 Enhanced While Loop
該 PEP 提議增加一個可選的 do 子句,支援將 while 迴圈擴充套件成這樣子:
do: <setup code> while <condition>: <loop body>
這不是簡單地從其它語言翻譯成 Python,它的 while 語句後保留了 Python 的縮排用法,並不會造成直譯形式的突兀結果。
加上 while 迴圈本身已支援的可選的 else 子句,因此,while 完整的語法結構是這樣的:
while_stmt : ["do" ":" suite] "while" expression ":" suite ["else" ":" suite]
(PS.在本系列的下一篇文章,我們將解釋為什麼 Python 要支援 while-else 語法)
也就是說,在保持原 while 迴圈語法不變的情況下,PEP-315 提議支援在 while 前面使用一個可選的 do 子句。
do 子句只會執行一遍,當它裡面出現 break 時,則跳出整個 do-while 迴圈;當 do 子句中出現 continue 時,則跳出 do 子句,進到 while 的條件判斷中。
有了 do 子句後,很容易就能實現 do {...} while (0)
的跳轉控制效果。
但是,這個 PEP 遭到了一些核心開發者的反對。
反對的理由是,不需要引入新的關鍵字和語法,僅使用現有語法就能很好地實現同樣的功能:
while True: <setup code> if not <condition>: break <loop body>
Python 之父 Guido van Rossum 也持反對意見,他的原話是:
Please reject the PEP. More variations along these lines won't make the language more elegant or easier to learn. They'd just save a few hasty folks some typing while making others who have to read/maintain their code wonder what it means.
簡單翻譯一下,這種 do-while 語法並不會使 Python 更優雅好用,反而會產生閱讀/維護程式碼的理解負擔。
就個人的感覺而言,我也不贊成引入 PEP-315 那種可選的 do-while 語法,雖然它比固定形式的 do-while 結構更為靈活和優雅一點。
最後稍微總結一下,do-while 作為一種常見的迴圈結構,在其它語言中有所發揮,它甚至還發展出了 do {...} while (0)
的典型用法,但是,do-while 能夠解決的幾個問題要麼在 Python 中並不存在(巨集定義、彙編指令),要麼就是已經有更為合適而低成本的實現(跳轉控制)。
看完這篇文章,你是否還有其它補充的內容呢?歡迎交流討論。
如果你對 Python 語言設計相關的話題感興趣,歡迎訂閱 Github 上的《Python為什麼》系列文章( https://github.com/chinesehuazhou/python-whydo)
關聯閱讀:
- 模板化的封裝,降低業務程式碼開發
- 分享一個 SpringCloud Feign 中所埋藏的坑
- MySQL 事務常見面試題總結 | JavaGuide 稽核中
- 型別安全的 Go HTTP 請求
- 從幾次事故引起的對專案質量保障的思考
- 聯盟鏈 Hyperledger Fabric 應用場景
- 上半年最中意的 GitHub 更新「GitHub 熱點速覽 v.22.21」
- 為什麼我寫了路由懶載入但程式碼卻沒有分割?
- 這個設計原則,你認同嗎?
- SpringCloud基礎概念學習筆記(Eureka、Ribbon、Feign、Zuul)
- 自動微分原理
- layui資料表格搜尋
- Python 中的記憶體管理
- spring 配置檔案 --bean
- 【leetcode】239. 滑動視窗最大值
- Spring 原始碼(17)Spring Bean的建立過程(8)Bean的初始化
- SpringBoot進階教程(七十四)整合ELK
- 連結串列的基本操作和高頻演算法題
- 【python】python連線Oracle資料庫
- Python技法:浮點數取整、格式化和NaN處理