摘要:普通可迭代對象只有魔術(shù)方法,而不像迭代器一樣擁有魔術(shù)方法,這意味著它無法實現(xiàn)自遍歷過程,同時在經(jīng)過循環(huán)的它遍歷后,也不會破壞原有的結(jié)構(gòu)。這兩個是我創(chuàng)造的概念,詳見進階迭代器與迭代器切片。
本文原創(chuàng)并首發(fā)于公眾號【Python貓】,未經(jīng)授權(quán),請勿轉(zhuǎn)載。
原文地址:https://mp.weixin.qq.com/s/Oy...
6 月 22 日,Python 之父 Guido 發(fā)了一條推特,說了 Python 的一則歷史故事,他說 elif 是從 C 語言中偷過來的:
elif 是“else if”的簡寫,用于條件判斷。當只有兩個分支時,我們會寫成“if...else...”,當出現(xiàn)更多分支時,我們會寫成如下格式:
if 判斷條件1: 做事情1 elif 判斷條件2: 做事情2 else: 做其它事
簡寫而成的 elif 不僅是減少了幾個字符,而且由于單一而清晰的用途,它還不會給我們帶來理解或使用上的困惑。
但是,簡寫法并不是主流,完整寫法才是主流,C 語言中就是采用完整的寫法:
if(判斷條件1) { 做事情1 } else if(判斷條件2) { 做事情2 } else { 做其它事 }
沒錯,C 語言使用的是全拼寫法,但是在它的預(yù)處理/預(yù)編譯語句中,還有一個 elif 指令,Guido 所說的“偷”,就是從這來的:
#if 常量表達式1 // 編譯1 #elif 常量表達式2 // 編譯2 #else // 編譯3 #endif
Python 沒有預(yù)編譯,所以所謂的偷,跟預(yù)編譯沒有關(guān)系,只是在對比兩種寫法后,借用了更簡潔的寫法而已。
為什么 C 語言不把兩種寫法統(tǒng)一起來呢?這我不得而知了,而 Guido 在兩種寫法中,選擇了后一種非主流卻更好用的寫法。我想對他說,你“偷”得好??!
實際上,留言區(qū)里的人也有同感,紛紛表示:不介意、很 okay、非常喜歡,還有人說“不是偷,而是收獲(harvested)”、“不是偷,而是把它提升了一些高度”……
前不久,我寫了一篇《聊聊 print 的前世今生》,print 這個詞就是從 C 語言中借用來的。除此之外,如果有人仔細比較這兩種語言的關(guān)鍵字和習慣命名,肯定會發(fā)現(xiàn)不少相同的內(nèi)容。
編程語言間有一些共享的元素,這很常見,創(chuàng)造一門語言并不意味著要原創(chuàng)每一個詞句,畢竟大部分思想是共通的,作為基礎(chǔ)設(shè)施的詞語更是如此。
那么,我突然好奇了:創(chuàng)造一門編程語言時,什么時候該借用,什么時候該創(chuàng)造呢?
這個問題看起來可能沒啥意義,因為終其一生,我們多數(shù)人也不大可能會參與創(chuàng)造一門編程語言。
但我覺得它還是極有意義的,首先,提問精神值得肯定,其次,它還提供了一種溯源、甄別、遴選、創(chuàng)造的體系性視角,我認為這是求知的正確思維方式。
帶著這個疑惑,我特別想要考察的是 Python 的 for 循環(huán)。
如果你有其它語言基礎(chǔ),就知道 “for 循環(huán)”通常指的是這樣的三段式結(jié)構(gòu):
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(" "); }
這種 C 風格的寫法是很初級的東西,不少語言都借用了。但是,它的寫法實在繁瑣,為了更方便地遍歷集合中的元素,人們在 for 循環(huán)之外又引入了升級版的 foreach 循環(huán):
// 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 循環(huán),但是,它借用有度,在設(shè)計上早早就有自己獨到的考慮,它直接摒棄了三段式的 for 循環(huán),而是采用類似 foreach 的一種寫法:
for iterating_var in sequence: statements(s) # 例子 for i in range(3): print(i) for i in "hello": print(i)
從表面上看,Python 的 for 循環(huán)跟其它語言的 foreach 很相似,但實際上,它的工作原理卻很不相同。
為什么會有不同呢?主要是因為 Python 的 for 語句用于可迭代對象上,而不僅僅是用于集合或者普通的容器(雖然它們也是可迭代對象),而可迭代對象還可再細分出迭代器與生成器,這會造成最終結(jié)果的極大差異。
先看看兩個例子:
# 例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__() 魔術(shù)方法,而不像迭代器一樣擁有 __next__() 魔術(shù)方法,這意味著它無法實現(xiàn) 自遍歷 過程,同時在經(jīng)過 for 循環(huán)的 它遍歷 后,也不會破壞原有的結(jié)構(gòu)。(這兩個是我創(chuàng)造的概念,詳見《Python進階:迭代器與迭代器切片》)。
但是,迭代器是一種匱乏的設(shè)計,具有單向損耗的特性,遍歷一次后就會被破壞掉,不能重復(fù)利用。(關(guān)于迭代器的設(shè)計問題,這篇文章值得一看《當談?wù)摰鲿r,我談些什么?》)。
這表明了,Python 中 for 循環(huán)的使用場景很廣闊,而且它還可能帶來非純結(jié)果,即重復(fù)執(zhí)行同樣的代碼塊,會出現(xiàn)不同的結(jié)果。
這是不是跟別的語言很不同了呢?相同的關(guān)鍵字,相似的循環(huán)思想與寫法,但是,帶來的影響卻有差別。
關(guān)于 Python 的 for 循環(huán),還有一個很獨特的設(shè)計,即 for-else 結(jié)構(gòu):
x = [1, 2, 3] for i in x: print(i, end = " ") else: print("ok") # 輸出:1 2 3 ok
本文開頭提到了 if-else 結(jié)構(gòu),只有在不滿足 if 條件時,才會執(zhí)行到 else 部分,也就是說,如果 if 語句為真,那執(zhí)行完它的語句塊后,就會跳過 else 部分。
這是一種非此即彼的并行關(guān)系 ,直白地說是“如果...就...;否則就...” 。
但是,對于 for-else 結(jié)構(gòu),for 語句并不是在做真值判斷,它的程序體必然會執(zhí)行(除非可迭代對象為空),執(zhí)行后還會繼續(xù)執(zhí)行 else 部分。
所以,它是一種先此后彼的串行關(guān)系 ,翻譯出來則是“對于...就...;然后...”。
這種結(jié)構(gòu)肯定不是從 C 語言中借用來的,至于是否為 Python 所獨創(chuàng),我不確定(大概率是,姑且認為是吧),如果有知情的同學,煩請告知。
那么,為什么 Python 要加上這種設(shè)計呢,它有什么實際的用途么?
x = [1,2,3] for i in x: if i % 2 == 0: print(i) # match break else: print("mismatch")
上例的 for 部分增加了一個判斷以及 break,這個 break 不僅會跳出 for 循環(huán)本身,還會跳過 else 部分。
上例的作用是查找偶數(shù),如果找到則打印出來,如果 for 循環(huán)遍歷完都找不到,則進入到 else 分支,打印“mismatch”的結(jié)果。
所以,其實 else 是 for 循環(huán)有沒有正常遍歷結(jié)束的標記,如果在循環(huán)后沒有達到某種目標而跳出(break、return 或者 raise),就可以在 else 中做必要的補充(記錄日志、拋出異常等等)。
這種設(shè)計并不算一個好的設(shè)計,因為 else 會帶來誤解(if-else 那種非此即彼的關(guān)系),而且它的最大用途需要結(jié)合 break 等跳出循環(huán)的操作,但是這層信息卻非顯而易見的。
在核心開發(fā)者的郵件列表里,就有不少爭論點,2009 年的這封郵件梳理了大家的討論(https://mail.python.org/pipermail/python-ideas/2009-October/006155.html)。
其中,有開發(fā)者提議:
移除這個寫法
如果用了卻沒寫 break,就生成告警提示
替換 else 關(guān)鍵字(如 then、finally、else no break)
增加其它的功能
這封郵件一一列舉了這些觀點的提出原因及改進想法,然后又一一地反駁了它們,最后的結(jié)論是保持 for-else 寫法不變,也就是大家現(xiàn)在看到的實現(xiàn)方式。它的完整語義是:
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 對標的是“是否執(zhí)行 break”,如果沒有 break,則進入else。
但是,我并不認可這種做法,因為 break 是隱含條件,在直觀上我們只看到了 for-else,很容易產(chǎn)生 if-else 那樣的聯(lián)想。因此,我反而贊同把 else 改為 then,以消除誤會。
這封郵件的反駁意見是,改成 then 會引入新的關(guān)鍵字,因此不好。
我認為這個說法有些牽強(從使用者的角度),還記得本文開頭的內(nèi)容么,elif 就是新引入的關(guān)鍵字啊,看看它現(xiàn)在是多受歡迎。
elif 屬于那種初看不知何意,但知道后肯定會記住的詞,而且也不大可能拼寫錯誤。為了這點簡潔易拼寫的好處,它就被引入成新的關(guān)鍵字了。
for-else 中的 else 屬于那種初看以為知道含義的詞,但實際卻表達著不同意思(準確地說是,由于不知道隱含條件,而造成的誤解),為了清晰語義的好處,我認為可以引入新的關(guān)鍵詞 then 來替代 else。
不過,我轉(zhuǎn)念一想,現(xiàn)在討論這個已經(jīng)沒有意義了,畢竟時間已經(jīng)過去了,那都是 10 年前的討論了。
如果在 Python 創(chuàng)造之初,或者在 Python 3 大版本改動之初,這個討論就被提出,那很可能 for-else 會被設(shè)計成 for-then ,then 會像引入 elif 關(guān)鍵詞一樣被引入。
如果是那樣,說不定 Guido 某天心血來潮說起這則歷史小故事,留言區(qū)又會出現(xiàn)一大片的贊同之聲呢。
聊到這里,意猶未盡,但主題似乎有點跑偏,我們來稍微總結(jié)幾個要點吧:
Python 從 C 中借用了 elif,受到贊許
Python 沒有借用 C 傳統(tǒng)的三段式 for 循環(huán)
Python 采用類似 foreach 的表達,但應(yīng)用范圍更廣
Python 的 for 循環(huán)由于迭代器的設(shè)計原因,會造成一些陷阱
Python 創(chuàng)造了 for-else 結(jié)構(gòu),它的隱含語義是 for-(if break)-else,曾有討論是否要創(chuàng)造新的關(guān)鍵詞替換 for-else,但是被否決了
本文談到的內(nèi)容很微小,好像沒有什么實際的幫助,不知道 elif 來源、不知道 for 循環(huán)的細節(jié)、不知道 for-else 的用途與爭論,這些統(tǒng)統(tǒng)都不會造成語言使用上的障礙。
但我還是那個觀點:
閱讀 Python 的歷史,從中你可以看到設(shè)計者們對功能細節(jié)的打磨過程,最終你就明白了,Python 是如何一步一步地發(fā)展成今天的樣子。
這在我看來挺有趣的,更加增進了我對于 Python 的了解,以后在編程到某些用法的時候,腦海里滿滿都是故事,它頓時也會變得立體生動起來。
如果你讀后有所收獲,或者產(chǎn)生了不同想法,歡迎來知識星球與我互動交流。
公眾號【Python貓】, 本號連載優(yōu)質(zhì)的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術(shù)寫作、優(yōu)質(zhì)英文推薦與翻譯等等,歡迎關(guān)注哦。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/45063.html
摘要:提出標準,允許腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作。所以,這個新標準并沒有改變單線程的本質(zhì)。事件循環(huán)主線程線程只會做一件事,就是從消息隊列里面取消息執(zhí)行消息,再取消息再執(zhí)行。工作線程是生產(chǎn)者,主線程是消費者。 最近項目中遇到了一個場景,其實很常見,就是定時獲取接口刷新數(shù)據(jù)。那么問題來了,假設(shè)我設(shè)置的定時時間為1s,而數(shù)據(jù)接口返回大于1s,應(yīng)該用同步阻塞還是異步?我們...
摘要:最重要的是,任何設(shè)計模式都是一把雙刃劍。另一個設(shè)計模式是迭代器,在語言中以循環(huán)的形式實現(xiàn)。我們可以換個方法,利用策略模式和適配器模式的變種,能夠完美處理這種情景。何時使用它單例模式也許是最簡單的設(shè)計模式了。 使用python解釋設(shè)計模式 原文地址有沒有好奇過設(shè)計模式是什么呢?在這篇文章中,我們將了解為什么設(shè)計模式是重要的,同時也會給出一些python的例子,解釋為什么以及在什么時候使用...
摘要:前端知識點總結(jié)高級持續(xù)更新中字符串什么是連續(xù)存儲多個字符的字符數(shù)組相同下標遍歷選取不同類型不同不通用所有字符串都無權(quán)修改原字符串,總是返回新字符串大小寫轉(zhuǎn)換統(tǒng)一轉(zhuǎn)大寫統(tǒng)一轉(zhuǎn)小寫何時不區(qū)分大小寫時,都需要先轉(zhuǎn)為一致的大小寫,再比較。 前端知識點總結(jié)——JS高級(持續(xù)更新中) 1.字符串 什么是: 連續(xù)存儲多個字符的字符數(shù)組 相同: 1. 下標 2. .length 3. 遍歷 4....
摘要:大數(shù)據(jù)應(yīng)用已廣泛深入我們生活的方方面面,涵蓋醫(yī)療交通金融教育體育零售等各行各業(yè)。百度大數(shù)據(jù)分析疾病分布情況。更多案例大數(shù)據(jù)數(shù)據(jù)挖掘在交通領(lǐng)域有哪些應(yīng)用教育領(lǐng)域應(yīng)用案例百度大腦人腦大數(shù)據(jù)押高考作文題。 大數(shù)據(jù)應(yīng)用已廣泛深入我們生活的方方面面,涵蓋醫(yī)療、交通、金融、教育、體育、零售等各行各業(yè)。 天氣預(yù)測應(yīng)用案例: 典型的案例即天氣預(yù)測。各類氣象指征瞬時發(fā)生,以典型的高頻復(fù)雜的形式出現(xiàn),給各...
閱讀 1685·2023-04-26 02:29
閱讀 3279·2021-10-11 10:58
閱讀 2961·2021-10-08 10:16
閱讀 3233·2021-09-24 09:47
閱讀 1630·2019-08-29 16:56
閱讀 2778·2019-08-29 11:03
閱讀 2075·2019-08-26 13:35
閱讀 3241·2019-08-26 13:34