這一節(jié)的目標(biāo)是寫出一個(gè)能執(zhí)行條件控制的 Python 短程序!在這個(gè)過程中栈虚,你會(huì)學(xué)到如何正確地寫出 Python 代碼塊袖外,如何優(yōu)雅地寫注釋,如何冷靜地應(yīng)對錯(cuò)誤與異常魂务,以及什么才是專業(yè)優(yōu)美的 Python 代碼風(fēng)格曼验。
A. 代碼塊與注釋
代碼塊 Code block
代碼塊是一組由代碼構(gòu)成的功能“單元”。一個(gè)代碼塊可以單獨(dú)運(yùn)行粘姜。比如一個(gè)函數(shù)(function)或一個(gè)類(class)定義鬓照、一個(gè)模塊(module)都是代碼塊,例如:
def print_parity(x):
if x % 2 == 0:
print(x, "is even")
else:
print(x, "is odd")
這是一個(gè)函數(shù)定義(function definition)孤紧,這個(gè)函數(shù)可以用來查詢某個(gè)輸入數(shù)字的奇偶性豺裆。這個(gè)函數(shù)就是一個(gè)代碼塊兒,能單獨(dú)運(yùn)行号显,多行語句共同實(shí)現(xiàn)一個(gè)功能臭猜。
.
行與縮進(jìn) Indentation
為了能讓編譯器或解釋器準(zhǔn)確地把一堆代碼劃分到各自獨(dú)立的代碼塊中去,不同的編程語言有不同的方法押蚤。很多編程語言利用大括號 {}
圍起一個(gè)代碼塊蔑歌,還有語言只用分號 ;
來表示一句語句的結(jié)束。Python 很特別活喊,以強(qiáng)制縮進(jìn)(indentation)表示代碼塊歸屬丐膝。
縮進(jìn)可以用 tab 鍵或空格縮進(jìn)量愧,但同一腳本中不能混用(雖然你肉眼看不出到底按了 tab 還是空格,但 Python 解釋器看得見……)帅矗。習(xí)慣上一個(gè)縮進(jìn)單位為4個(gè)空格偎肃,一般 tab 鍵會(huì)默認(rèn)4個(gè)空格的長度。如果用 PyCharm 這類 IDE 編程浑此,一般不需要特別操心縮進(jìn)混用或長度問題累颂;但如果用單獨(dú)的文本編輯器,如 notepad++凛俱、TextMate 這些紊馏,就得在偏好里勾上類似"replace tabs as spaces"的設(shè)置選項(xiàng),否則特別容易報(bào)錯(cuò)蒲犬。
那什么時(shí)候需要縮進(jìn)呢朱监?簡單來說,在 Python 代碼里看到冒號 :
時(shí)就說明這個(gè)語句(statement)還沒說完原叮,還有下個(gè)語句赫编,此時(shí)后面的語句若另起一行就必須縮進(jìn)。比如上面 print_parity
這個(gè)函數(shù)定義里就出現(xiàn)了3個(gè)冒號奋隶,而每個(gè)冒號緊跟著的下一句都縮進(jìn)了擂送。
由于縮進(jìn)方式或長度不統(tǒng)一而導(dǎo)致運(yùn)行錯(cuò)誤,會(huì)在報(bào)錯(cuò)信息里看到 IndentationError
的提示唯欣。
關(guān)于更多運(yùn)行 Python 可能出現(xiàn)的錯(cuò)誤與異常嘹吨,在本節(jié)后半部分會(huì)專門細(xì)說。
.
空行
專門把空行拎出來境氢,是因?yàn)榭招幸彩且幻逗贸绦虻囊徊糠煮翱健T诤瘮?shù)定義(function definition)、類定義(class definition)代碼塊之間一般空1-3行产还,具體是1行還是3行主要看代碼塊之間的邏輯關(guān)系匹厘。在 PyCharm IDE 中嘀趟,編輯器會(huì)提示如何空行脐区。
.
注釋 Comments
注釋不僅是為了向所有可能看到你代碼的人間接解釋你的程序在干嘛,更為了讓你自己將來回顧時(shí)還能理解自己曾寫過的代碼她按。因此牛隅,注釋的基本原則就是只簡潔地解釋那些其他程序員可能看不懂或要停下來思考半天才能理解的代碼。
.
Python 中注釋分兩大類:單行注釋與多行注釋酌泰。
單行注釋以 #
開頭媒佣,如:
# calculate the area of a square
square_side = 2.0
square_area = square_side ** 2
print(square_area)
多行注釋則以三對單引號 ‘’‘
或三對雙引號 “”“
將注釋內(nèi)容括起來,如:
def square_area(side):
”“”
returns the area of a square.
:param side: length of one side of the sqaure
“”“
area = side * 2
return area
多行注釋多用于像上面例子中函數(shù)定義中陵刹,用于解釋某個(gè)函數(shù)的目的默伍。
.
細(xì)心讀者也許會(huì)發(fā)現(xiàn)在上面的兩個(gè)注釋舉例中,尤其是第一個(gè)單行注釋例子,注釋本身顯得很多余也糊,因?yàn)樽兞棵呀?jīng)足夠清楚準(zhǔn)確——這就是好的編程風(fēng)格(programming style)炼蹦;與其費(fèi)時(shí)間專門為變量名注釋一大堆,不如一開始就起個(gè)更清晰易懂的名稱狸剃。
關(guān)于編程風(fēng)格掐隐,我還會(huì)在本節(jié)末尾多說幾句。
.
B. 流程控制之條件語句
流程控制 Flow control
小學(xué)時(shí)钞馁,一次暑假作業(yè)集(那時(shí)叫《暑假園地》)上面有道題虑省,我至今依然記得。
題目是一幅漫畫僧凰,畫了“小白”一人在家的畫面探颈。勤勞的小白同學(xué)需要做:
- 掃地,用時(shí)30min
- 燒熱水训措,用時(shí)10min
- 灑水膝擂,用時(shí)10min
- (用熱水)熱牛奶,用時(shí)10min
- 把臟衣服放進(jìn)洗衣機(jī)隙弛,洗衣程序需要90min
- 晾衣服架馋,用時(shí)20min
- 擦桌子,用時(shí)15min
- 給花澆水全闷,用時(shí)5min
你需要在完成所有任務(wù)的前提下叉寂,給小白安排一個(gè)用時(shí)最短的流程。
我之所以一直記得這道小學(xué)題目总珠,是因?yàn)樵谥蟮暮芏鄨鼍跋缕流ⅲ叶悸?lián)想到過這個(gè)簡單的流程題。當(dāng)我剛接觸到編程的基礎(chǔ)思想時(shí)(在中學(xué)數(shù)學(xué)課上學(xué)畫流程圖)局服,一拍腦袋钓瞭,這不就是小學(xué)的任務(wù)流程題嘛!
其實(shí)從宏觀角度來看淫奔,流程控制就是編程的中心議題之一山涡。為了實(shí)現(xiàn)一個(gè)或一組功能,一個(gè)程序需要完成若干個(gè)小任務(wù)唆迁。有些任務(wù)像給花澆水和擦桌子一樣無所謂先后鸭丛,但有些任務(wù)之間存在著必須“先洗衣服才能晾衣”這樣的先后次序,還有些任務(wù)像燒熱水一樣可以和其他任務(wù)“并聯(lián)”唐责,默默像背景音樂一樣運(yùn)行鳞溉。這樣的流程控制在 Python 中可以依靠很多工具實(shí)現(xiàn),條件語句就是其中一種鼠哥。
.
條件控制
我們已經(jīng)在上文求奇偶性的函數(shù)定義例子中見過條件控制語句(conditional statement)了熟菲,再來看一個(gè)例子:
def compare(x, y):
if x < y:
print(x, "is less than", y)
elif x > y:
print(x, "is greater than", y)
else:
print(x, "and", y, "are equal")
這是個(gè)比較兩個(gè)輸入值(不一定是數(shù)字)的函數(shù)看政。比較兩個(gè)值(x & y)的結(jié)論只可能在這三個(gè)中挑一個(gè):x < y
,x > y
或 x = y
抄罕。查詢一個(gè)數(shù)字奇偶性的函數(shù)也類似帽衙,一個(gè)數(shù)字只可能在奇數(shù)或偶數(shù)中挑一個(gè)歸屬,不存在其他可能性贞绵。當(dāng)遇到這樣的情景時(shí)厉萝,就適合用條件控制來表達(dá)。
.
從這兩個(gè)例子中榨崩,我們可以抽象出條件控制語句的一般形式:
if condition_1:
statement_block_1
elif condition_2:
statement_block_2
else:
statement_block_3
這里出現(xiàn)了三個(gè) Python 關(guān)鍵詞:if/elif/else
谴垫,elif
是 else if 的縮寫,即除了 if
以外的條件母蛛。當(dāng)解釋器走到這兒時(shí)翩剪,
- 先查看
if
語句的條件(condition_1)是否滿足,如果滿足(即為True)彩郊,便執(zhí)行 statement_block_1前弯,并直接跳過后面所有elif/else
語句塊; - 若不滿足 condition_1秫逝,再判斷
elif
語句中的 condition_2 是否為 True恕出; - 若 condition_2 為 True,則執(zhí)行 statement_block_2违帆;
- 若 condition_2 為 False浙巫,就執(zhí)行
else
語句下的 statement_block_3.
三個(gè)條件語句分支(branching)并不非得同時(shí)存在,比如求奇偶性的例子就只有 if/else
刷后,if
語句塊也可單獨(dú)存在的畴;還可以有三個(gè)以上的條件,此時(shí)可以多次利用 elif
語句塊尝胆。
注意:每個(gè)條件語句(if/elif/else
statement)后都要加冒號 :
丧裁,冒號后的語句(或者也可以稱為子句 clause)若另起一行則需要縮進(jìn);else
語句塊必須放在末尾含衔。
.
描述再多也不如一張圖直觀煎娇,多個(gè)條件控制語句的代碼塊可以用這個(gè)流程圖來表示鏈?zhǔn)綏l件 (chained conditionals) ——即一連串彼此平行的條件語句塊:
思考時(shí)間:上面已經(jīng)給出了如何比較兩個(gè)輸入值的函數(shù)定義,那么如果要比較三個(gè)輸入值呢 (為簡化問題抱慌,假設(shè)這三個(gè)輸入值<u>互不相同</u>) 逊桦?你會(huì)怎樣設(shè)計(jì)這個(gè)條件控制代碼塊的結(jié)構(gòu)眨猎?可以先畫一畫流程圖抑进。
.
這個(gè)問題可以先倒著思考,比較兩個(gè)值一共只有三種結(jié)果睡陪,那比較三個(gè)不同的值可能出現(xiàn)幾種結(jié)果呢寺渗?
.
我提供一種典型思路:
def compare(x, y, z):
if x > y:
if y > z:
print("x > y > z")
elif z > x:
print("z > x > y")
else:
print("x > z > y")
else:
if x > z:
print("y > x > z")
elif z > y:
print("z > y > x")
else:
print("y > z > x")
在這個(gè)函數(shù)定義中匿情,條件控制語句似乎更復(fù)雜了。在第一個(gè) if
語句后信殊,又套了一個(gè) if/elif/else
語句塊炬称;在第一層的 else
語句后,也套了個(gè)新的 if/elif/else
語句塊涡拘。像這樣多層條件語句塊玲躯,被稱為嵌套條件(nested conditionals)。這個(gè)例子只嵌套了兩層條件語句塊鳄乏,但實(shí)際上可以多層嵌套跷车。
嵌套條件的流程圖可以是這樣:
仔細(xì)觀察的話,會(huì)發(fā)現(xiàn)嵌套條件的流程圖與鏈?zhǔn)綏l件的差別在于:嵌套條件里至少有一個(gè)判斷為 True 的分支下橱野,會(huì)再加一個(gè)條件判斷菱形朽缴。
.
C. 錯(cuò)誤與異常
當(dāng)你開始學(xué)習(xí)編程后,最不陌生的反饋應(yīng)該就是來自計(jì)算機(jī)的報(bào)錯(cuò)信息(error message)水援∶芮浚看到報(bào)錯(cuò)沒什么可緊張的,就算是經(jīng)驗(yàn)豐富的程序員蜗元,也不太可能一次就把一個(gè)程序毫無錯(cuò)誤地從頭寫到底或渤。報(bào)錯(cuò)信息只是 Python 想要溫柔地告訴你哪里出了問題的方式 :)
在看懂報(bào)錯(cuò)信息前,我們首先要了解下在 Python 編程中可能會(huì)出現(xiàn)什么錯(cuò)誤奕扣。
第一大類錯(cuò)誤是語法錯(cuò)誤(syntactic errors)劳坑,新手最易犯,即寫出一些 Python 語法不允許的表達(dá)式(illegal expressions)成畦,比如忘記加冒號距芬、忘記縮進(jìn)等。出現(xiàn)語法錯(cuò)誤的代碼不能通過編譯循帐。好消息是 Python 語法分析器會(huì)幫你找出所有語法錯(cuò)誤框仔,并以 SyntaxError:
的信息來提示你具體錯(cuò)誤在哪里,比如:
>>> print "abc"
File "<stdin>", line 1
print "a"
^
SyntaxError: Missing parentheses in call to 'print'
這條報(bào)錯(cuò)信息就非常清楚地告訴我:1. 是語法錯(cuò)誤拄养;2. 具體錯(cuò)誤是沒給 print
加括號离斩;3. 出錯(cuò)位置在第一行(向上尖箭頭指著檢查出錯(cuò)的具體位置,但這個(gè)指示不表示錯(cuò)誤僅在尖箭頭處出現(xiàn)瘪匿,還可能在附近)跛梗。
.
當(dāng)語法正確時(shí),還可能出現(xiàn)語義錯(cuò)誤(semantic errors)棋弥。語義錯(cuò)誤指的是代碼的含義出了點(diǎn)問題核偿。
語義分為兩大類:
- 靜態(tài)語義(static semantics): 如,這句代碼是否有意義顽染;
- 全語義(full semantics): 這個(gè)程序的目的是什么漾岳?會(huì)生成什么結(jié)果轰绵?
靜態(tài)語義錯(cuò)誤也叫異常(exception)或運(yùn)行時(shí)錯(cuò)誤(runtime error),Python 會(huì)在運(yùn)行時(shí)幫忙判斷一部分靜態(tài)語義錯(cuò)誤尼荆,以 Traceback 的形式返回報(bào)錯(cuò)信息左腔。這里示范兩個(gè)靜態(tài)語義錯(cuò)誤:
>>> a + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> a = '3'
>>> a + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: must be str, not int
這兩條 Traceback 指出了異常的類型:分別是 NameError
和 TypeError
;還告訴了出錯(cuò)的位置捅儒。
為什么會(huì)報(bào)異常呢液样?因?yàn)?Python 解釋器不能理解代碼的含義是什么:第一條,變量 a 沒賦值巧还;第二條蓄愁,字符串與整型不能相加 (相加無意義)。
.
全語義的錯(cuò)誤 Python 就無能為力了狞悲。因此有時(shí)就算你寫的程序可以正常運(yùn)行撮抓,也吐出一些似乎正常的結(jié)果了,也不代表這個(gè)程序就沒有任何錯(cuò)誤摇锋。
為了減少編程中可能產(chǎn)生的錯(cuò)誤(bug)丹拯,降低后期捉蟲(debug)的時(shí)間,學(xué)會(huì)一套專業(yè)優(yōu)雅的編程風(fēng)格絕對是必要的荸恕。
.
D. Develop Good Style 風(fēng)格指南
說到底乖酬,所謂風(fēng)格就是編程習(xí)慣;好的編程風(fēng)格不僅讓你看起來更專業(yè)融求,還能讓你的代碼更易于維護(hù)咬像,編程時(shí)心情也更愉悅 :)
在現(xiàn)階段已經(jīng)可以依樣畫瓢遵循的風(fēng)格指南有:
- 寫好注釋,讓自己一年后也依然能讀懂這段代碼生宛;
- 取有意義的變量名县昂,讓業(yè)內(nèi)人士一眼能看懂這個(gè)變量代指的含義;
- 合理空格陷舅;
- 不要隨意改變變量的類型(type)倒彰;
- 測試一個(gè)代碼塊中的所有分支(branch),確保每個(gè)分支都有結(jié)果(fruitful)莱睁。
還有更多更多寫出優(yōu)美代碼的 Python 編程準(zhǔn)則待讳,在今后的教程中還會(huì)不定時(shí)地繼續(xù)插入更多。
.
第5節(jié) 作業(yè)
- 閱讀 Think Python: How to Think Like a Computer Scientist (2nd Ed) Chapter 5.1-5.7
- 完成 Think Python Chapter 5 課后習(xí)題 5.2.1, 5,3,1.
第5節(jié) 小結(jié)
這一節(jié)的重點(diǎn)是學(xué)會(huì)寫條件控制代碼塊仰剿。此外创淡,我們初步讀懂了各種語法、語義報(bào)錯(cuò)信息南吮,還初步建立了對優(yōu)雅 Python 編程風(fēng)格的印象琳彩。已經(jīng)可以用 Python 代碼來表達(dá)更多想法了呢!
.
.