本文原創並首發於公衆號【Python貓】,未經受權,請勿轉載。html
原文地址:https://mp.weixin.qq.com/s/OypPwnJ2vX2vJtZRkVa-Ugjava
6 月 22 日,Python 之父 Guido 發了一條推特,說了 Python 的一則歷史故事,他說 elif 是從 C 語言中偷過來的:python
elif 是「else if」的簡寫,用於條件判斷。當只有兩個分支時,咱們會寫成「if...else...」,當出現更多分支時,咱們會寫成以下格式:編程
if 判斷條件1: 作事情1 elif 判斷條件2: 作事情2 else: 作其它事
簡寫而成的 elif 不只是減小了幾個字符,並且因爲單一而清晰的用途,它還不會給咱們帶來理解或使用上的困惑。編程語言
可是,簡寫法並非主流,完整寫法纔是主流,C 語言中就是採用完整的寫法:ide
if(判斷條件1) { 作事情1 } else if(判斷條件2) { 作事情2 } else { 作其它事 }
沒錯,C 語言使用的是全拼寫法,可是在它的預處理/預編譯語句中,還有一個 elif 指令,Guido 所說的「偷」,就是從這來的:oop
#if 常量表達式1 // 編譯1 #elif 常量表達式2 // 編譯2 #else // 編譯3 #endif
Python 沒有預編譯,因此所謂的偷,跟預編譯沒有關係,只是在對比兩種寫法後,借用了更簡潔的寫法而已。ui
爲何 C 語言不把兩種寫法統一塊兒來呢?這我不得而知了,而 Guido 在兩種寫法中,選擇了後一種非主流卻更好用的寫法。我想對他說,你「偷」得好啊!idea
實際上,留言區裏的人也有同感,紛紛表示:不介意、很 okay、很是喜歡,還有人說「不是偷,而是收穫(harvested)」、「不是偷,而是把它提高了一些高度」……翻譯
前不久,我寫了一篇《聊聊 print 的前世此生》,print 這個詞就是從 C 語言中借用來的。除此以外,若是有人仔細比較這兩種語言的關鍵字和習慣命名,確定會發現很多相同的內容。
編程語言間有一些共享的元素,這很常見,創造一門語言並不意味着要原創每個詞句,畢竟大部分思想是共通的,做爲基礎設施的詞語更是如此。
那麼,我忽然好奇了:創造一門編程語言時,何時該借用,何時該創造呢?
這個問題看起來可能沒啥意義,由於終其一輩子,咱們多數人也不大可能會參與創造一門編程語言。
但我以爲它仍是極有意義的,首先,提問精神值得確定,其次,它還提供了一種溯源、甄別、遴選、創造的體系性視角,我認爲這是求知的正確思惟方式。
帶着這個疑惑,我特別想要考察的是 Python 的 for 循環。
若是你有其它語言基礎,就知道 「for 循環」一般指的是這樣的三段式結構:
for ( init; condition; increment ){ statement(s); } // java for(int x = 10; x < 20; x = x+1) { System.out.print("value of x : " + x ); System.out.print("\n"); }
這種 C 風格的寫法是很初級的東西,很多語言都借用了。可是,它的寫法實在繁瑣,爲了更方便地遍歷集合中的元素,人們在 for 循環以外又引入了升級版的 foreach 循環:
// java int[] a = {1,2,3}; for(int i : a){ System.out.print(i + ","); } // C# int[] a = {1,2,3}; foreach(int i in a){ System.Console.WriteLine(i); }
Python 中也有 for 循環,可是,它借用有度,在設計上早早就有本身獨到的考慮,它直接摒棄了三段式的 for 循環,而是採用相似 foreach 的一種寫法:
for iterating_var in sequence: statements(s) # 例子 for i in range(3): print(i) for i in "hello": print(i)
從表面上看,Python 的 for 循環跟其它語言的 foreach 很類似,但實際上,它的工做原理卻很不相同。
爲何會有不一樣呢?主要是由於 Python 的 for 語句用於可迭代對象上,而不只僅是用於集合或者普通的容器(雖然它們也是可迭代對象),而可迭代對象還可再細分出迭代器與生成器,這會形成最終結果的極大差別。
先看看兩個例子:
# 例1,普通可迭代對象 x = [1, 2, 3] for i in x: print(i) for i in x: print(i) # 例2,迭代器或生成器 y = iter([1, 2, 3]) # y = (i for i in [1,2,3]) for i in y: print(i) for i in y: print(i)
例 1 中,「1 2 3」會被打印兩次,而在例 2 中,則只會打印一次。
普通可迭代對象只有 __iter__() 魔術方法,而不像迭代器同樣擁有 __next__() 魔術方法,這意味着它沒法實現 自遍歷
過程,同時在通過 for 循環的 它遍歷
後,也不會破壞原有的結構。(這兩個是我創造的概念,詳見《Python進階:迭代器與迭代器切片》)。
可是,迭代器是一種匱乏的設計,具備單向損耗的特性,遍歷一次後就會被破壞掉,不能重複利用。(關於迭代器的設計問題,這篇文章值得一看《當談論迭代器時,我談些什麼?》)。
這代表了,Python 中 for 循環的使用場景很廣闊,並且它還可能帶來非純結果,即重複執行一樣的代碼塊,會出現不一樣的結果。
這是否是跟別的語言很不一樣了呢?相同的關鍵字,類似的循環思想與寫法,可是,帶來的影響卻有差異。
關於 Python 的 for 循環,還有一個很獨特的設計,即 for-else 結構:
x = [1, 2, 3] for i in x: print(i, end = " ") else: print("ok") # 輸出:1 2 3 ok
本文開頭提到了 if-else 結構,只有在不知足 if 條件時,纔會執行到 else 部分,也就是說,若是 if 語句爲真,那執行完它的語句塊後,就會跳過 else 部分。
這是一種非此即彼的並行關係 ,直白地說是「若是...就...;不然就...」 。
可是,對於 for-else 結構,for 語句並非在作真值判斷,它的程序體必然會執行(除非可迭代對象爲空),執行後還會繼續執行 else 部分。
因此,它是一種先此後彼的串行關係 ,翻譯出來則是「對於...就...;而後...」。
這種結構確定不是從 C 語言中借用來的,至因而否爲 Python 所首創,我不肯定(大機率是,姑且認爲是吧),若是有知情的同窗,煩請告知。
那麼,爲何 Python 要加上這種設計呢,它有什麼實際的用途麼?
x = [1,2,3] for i in x: if i % 2 == 0: print(i) # match break else: print("mismatch")
上例的 for 部分增長了一個判斷以及 break,這個 break 不只會跳出 for 循環自己,還會跳過 else 部分。
上例的做用是查找偶數,若是找到則打印出來,若是 for 循環遍歷完都找不到,則進入到 else 分支,打印「mismatch」的結果。
因此,其實 else 是 for 循環有沒有正常遍歷結束的標記,若是在循環後沒有達到某種目標而跳出(break、return 或者 raise),就能夠在 else 中作必要的補充(記錄日誌、拋出異常等等)。
這種設計並不算一個好的設計,由於 else 會帶來誤解(if-else 那種非此即彼的關係),並且它的最大用途須要結合 break 等跳出循環的操做,可是這層信息卻非顯而易見的。
在覈心開發者的郵件列表裏,就有很多爭論點,2009 年的這封郵件梳理了你們的討論(https://mail.python.org/pipermail/python-ideas/2009-October/006155.html)。
其中,有開發者提議:
這封郵件一一列舉了這些觀點的提出緣由及改進想法,而後又一一地反駁了它們,最後的結論是保持 for-else 寫法不變,也就是你們如今看到的實現方式。它的完整語義是:
execute the for-loop (or while-loop) if you reach a `break`, jump to the end of the `for...else` block else execute the `else` suite
也就是說,else 對標的是「是否執行 break」,若是沒有 break,則進入else。
可是,我並不承認這種作法,由於 break 是隱含條件,在直觀上咱們只看到了 for-else,很容易產生 if-else 那樣的聯想。所以,我反而贊同把 else 改成 then,以消除誤會。
這封郵件的反駁意見是,改爲 then 會引入新的關鍵字,所以很差。
我認爲這個說法有些牽強(從使用者的角度),還記得本文開頭的內容麼,elif 就是新引入的關鍵字啊,看看它如今是多受歡迎。
elif 屬於那種初看不知何意,但知道後確定會記住的詞,並且也不大可能拼寫錯誤。爲了這點簡潔易拼寫的好處,它就被引入成新的關鍵字了。
for-else 中的 else 屬於那種初看覺得知道含義的詞,但實際卻表達着不一樣意思(準確地說是,因爲不知道隱含條件,而形成的誤解),爲了清晰語義的好處,我認爲能夠引入新的關鍵詞 then 來替代 else。
不過,我轉念一想,如今討論這個已經沒有意義了,畢竟時間已通過去了,那都是 10 年前的討論了。
若是在 Python 創造之初,或者在 Python 3 大版本改動之初,這個討論就被提出,那極可能 for-else 會被設計成 for-then ,then 會像引入 elif 關鍵詞同樣被引入。
若是是那樣,說不定 Guido 某天心血來潮提及這則歷史小故事,留言區又會出現一大片的贊同之聲呢。
聊到這裏,意猶未盡,但主題彷佛有點跑偏,咱們來稍微總結幾個要點吧:
本文談到的內容很微小,好像沒有什麼實際的幫助,不知道 elif 來源、不知道 for 循環的細節、不知道 for-else 的用途與爭論,這些通通都不會形成語言使用上的障礙。
但我仍是那個觀點:
> 閱讀 Python 的歷史,從中你能夠看到設計者們對功能細節的打磨過程,最終你就明白了,Python 是如何一步一步地發展成今天的樣子。
這在我看來挺有趣的,更加增進了我對於 Python 的瞭解,之後在編程到某些用法的時候,腦海裏滿滿都是故事,它頓時也會變得立體生動起來。
若是你讀後有所收穫,或者產生了不一樣想法,歡迎來知識星球與我互動交流。
公衆號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫做、優質英文推薦與翻譯等等,歡迎關注哦。