在學(xué)習(xí)編程的過程中,起初新手開發(fā)者對程序沒有什么概念,先講解習(xí)慣和注意事項,不但沒什么效果,注意事項太多,反而提高了編程的難度;往往在自己遇到問題后,經(jīng)過思考,印象更加深刻。之前在每一講課后練習(xí)中,也加入了一些技巧說明,但比較分散。本講將總結(jié)編寫程序過程中遇到的各種問題和編程習(xí)慣。
20.1 編程習(xí)慣
寫程序最重要的是實現(xiàn)功能,在實現(xiàn)功能的基礎(chǔ)上,好的編程習(xí)慣,讓代碼更清晰,更容易理解,無論是過一段時間自己再看,還是給別人使用都能節(jié)約大量時間;同時,好的編程習(xí)慣讓代碼在不同運行環(huán)境和操作系統(tǒng)中也能穩(wěn)定地運行。
20.1.1 縮進(jìn)
縮進(jìn)指代碼與邊界之間的距離,Python使用縮進(jìn)組織代碼塊,一般用冒號和縮進(jìn)區(qū)分代碼之間的層次。代碼塊縮進(jìn)常出現(xiàn)在:函數(shù)體、循環(huán)體、以及判斷語句之后。
對Python編程來說,縮進(jìn)是必不可少的;其它編程語言,也大都包括縮進(jìn),但有的不是必須縮進(jìn),比如C語言用大括號括住循環(huán)體內(nèi)容,但一般程序員也會使用空格縮進(jìn),這樣更容易看到代碼的層次:直觀地看到循環(huán)從哪里開始,到哪里結(jié)束,縮進(jìn)也是一種良好的編程習(xí)慣。
1.縮進(jìn)相關(guān)錯誤
<pre style="tab-stops:21.75pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt">
縮進(jìn)的英文是Indentation,在報錯信息中看到相關(guān)的單詞,如“unexpected indent”,就需要注意,可能是縮進(jìn)錯誤。</pre>
<pre style="tab-stops:21.75pt 91.6pt 137.4pt 183.2pt 229.0pt 274.8pt 320.6pt 366.4pt 412.2pt 458.0pt 503.8pt 549.6pt 595.4pt 641.2pt 687.0pt 732.8pt">
有一種常見的錯誤是:縮進(jìn)中間斷開,如下例所示:</pre>
01 if x>0:
02 print("x>0")
03 print(x)
04 print("ok")
編寫這段語句的本意是:需要在x大于0時打印02和04句,無論什么情況都打印第03句。但使用以上寫法,第三句沒有縮進(jìn),表示if條件對應(yīng)的代碼塊在02句之后已經(jīng)結(jié)束,第04句的縮進(jìn)出錯。
冒號表示一個新代碼塊的開始,行尾的冒號和新增的縮進(jìn)一一對應(yīng),只有冒號沒有新的縮進(jìn),或者沒有冒號但有新的縮進(jìn)都是錯誤的,代碼塊中間不能斷開。
正確的方法是調(diào)換第03句和04句的位置。
2.縮進(jìn)的空格數(shù)
縮進(jìn)使用的空格數(shù)是可變的,一般情況下,使用四格縮進(jìn),但也有人習(xí)慣兩空格縮進(jìn)或八空格縮進(jìn)。在同一程序中必須使用一致的縮進(jìn)規(guī)則,建議使用默認(rèn)的四格縮進(jìn)。
3.Tab鍵生成縮進(jìn)和空格生成縮進(jìn)
Tab鍵輸入制表符,用于文本對齊,制表符在不同軟件中被顯示成四個或者八個空格。在Jupyter Notebook或者Spyder等編程工具中,使用Tab鍵生成的制表符都被轉(zhuǎn)換成了對應(yīng)的空格,因此可以使用Tab鍵生成縮進(jìn)。但使用Windows記事本這類純文本編輯器時,不自動轉(zhuǎn)換成空格,如果混用Tab鍵輸入的制表符和空格,程序運行時就會報錯,由于制表符和空格看起來差不多,這種錯誤很難通過觀察發(fā)現(xiàn)。
20.1.2 命名
無論是編寫程序還是使用文件都會遇到命名問題,比如函數(shù)名、類名、變量名、文件名、路徑名、網(wǎng)址、數(shù)據(jù)庫的字段名等等。計算機(jī)上的命名規(guī)則也大同小異。一般都支持英文字母、數(shù)字、下劃線,有的命名也支持中文、特殊符號、空格等等,雖然有些命名規(guī)則支持上百個字符的命名長度,但習(xí)慣上一般都在20字符以內(nèi),不應(yīng)太長。
1.有意義的命名
命名最重要的是有意義,讓自己和他人通過名字可以了解變量或者文件的作用。盡量不要使用a、b這樣的命名,很難查找,時間長了自己也會忘記用途。
2.中文和特殊符號
Python 3之后的版本,支持用中文命名變量,Windows也支持用中文命名文件和目錄。由于在計算機(jī)中支持多種中文編碼,使用不同軟件或者系統(tǒng)打開文件時,默認(rèn)的編碼方式如果不同,則可能出現(xiàn)亂碼;另外,有些系統(tǒng)區(qū)分大小寫字母,有的則視大小寫為同一字母。
在編寫程序時,盡量使用小寫英文字母數(shù)字和下劃線命名,最好使用有意義的英文單詞,其次是使用拼音,盡量少使用中文命名變量、函數(shù)和類,除了下劃線“_”,命名時也盡量少使用其它的特殊符號和空格,如果想用一個以上的詞命名變量,建議使用下劃線分隔詞,例如“get_width”相比“getwidth”更加直觀。
另外,還需要注意區(qū)分下劃線“_”和減號“-”;中英文標(biāo)點符號不同;一些字符看起來比較像,比如大寫的i(I)和小寫的L(l),英文字母O和數(shù)字0比較容易混淆等等。
3.命名習(xí)慣
注意根據(jù)用途命名,例如:主要給老師、同學(xué)、家長(中國人)看的文件名、目錄名可以用中文命名;給程序員看的代碼文件名、代碼中的內(nèi)容、數(shù)據(jù)庫字段使用英文字母命名;提供給別人調(diào)用的文件或者函數(shù)命名要有意義,只供自己使用的內(nèi)部變量可相對隨意一些。
另外,還有一些約定俗成的用法,比如:一般使用字母i,j表示循環(huán)中的記數(shù)變量。對于一些不變的值,通常使用大寫字母命名,比如窗口的長寬常使用WIDTH和HEIGHT命名。
20.1.3 注釋
1.用途
注釋是對代碼的解釋和說明,它的目的是讓人能夠更加輕松地理解代碼。雖然使用有意義的命名可以描述程序文件、函數(shù)、變量的功能,但命名不能太長,一般通過注釋進(jìn)行更加詳細(xì)的說明。注釋有以下幾種使用場景:
- 在程序文件的開頭介紹該程序的功能和用法。
- 在函數(shù)之前介紹函數(shù)的功能和用法。
- 在難以理解的語句前后,說明語句的功能。
- 將暫時不用的代碼注釋掉。
注釋要盡量寫得簡潔而清楚,如果不熟悉英文,就用中文寫,內(nèi)容比形式更重要。不光自己看得懂,也要讓別人能看懂。
2.用法
Python使用井號“#”實現(xiàn)單行注釋,當(dāng)前行中井號之后的內(nèi)容都視為注釋。使用三引號實現(xiàn)多行注釋,形如:
01 print('aaa') # 打印信息
02 """
03 注釋第一行
04 注釋第二行
05 """
三引號用于定義包含回車換行的字符串,可以是三個單引號,也可以是三個雙引號。在程序中的字符串,不操作、不賦值,也不影響程序的運行,因此,常作為包含多行的注釋使用。
20.2 調(diào)試程序
學(xué)習(xí)自然語言時,除了記住詞義以外,還要能組合成句子文章,才能正常使用。編程語言也是一種語言,學(xué)習(xí)時不僅要記住所學(xué)知識點,還要學(xué)習(xí)程序整體的構(gòu)造和調(diào)試。在每一講后面的習(xí)題可供讀者練習(xí)編寫各種程序。尤其是從第十五章之后,不再對課程中實例的簡單修改,而是用完全不同的代碼構(gòu)造新的功能。
如果讀者按要求完成了練習(xí),在練習(xí)過程中一定遇到了很多的程序錯誤,也從中學(xué)習(xí)了解決問題的方法。
本小節(jié)將總結(jié)常見的問題和解法,并介紹簡單有效的程序調(diào)試方法。
20.2.1 常見問題
無論是使用命令行還是Python集成開發(fā)環(huán)境,程序運行出錯時都會顯示錯誤提示。提示信息中最重要的是行號信息,開發(fā)者通過行號可以確定錯誤的大概位置。
1.語法錯誤

圖20.1是一種常見的錯誤,“invalid syntax”意思是語法錯誤,提示錯誤出現(xiàn)在第二行(line 2),實際上,錯誤的原因是第一行少了右括號,程序一直在等待右括號,而在第02行卻出現(xiàn)了print語句,所以提示為第02行錯誤。由此可見,也有少量錯誤行提示,可能是由之前行的錯誤引起的。但至少可以通過行號確認(rèn)大概位置。
2.縮進(jìn)錯誤
圖20.2是縮進(jìn)錯誤,“unexpected indent”意思是意外的縮進(jìn),它由第2行多了一個空格引起,也是常見的輸入引起的錯誤。

3.名字未定義錯誤
圖20.3示例了未定義錯誤,程序在使用y之前,沒有定義y,因此提示“name ‘y’ is not defined”(名字’y’沒有被定義)。有時候,并不是使用變量或者函數(shù)前沒有定義,也可能是沒有引入函數(shù)所在的三方庫、或者是輸入時拼寫錯誤引起的,比如:混淆字母O和數(shù)字0,逗號看成句號,冒號和分號寫錯等等。像大寫I(i)和小寫l(L)混淆的錯誤很難被觀察到,這時,可以使用查找功能,比如在Jupyter Notebook中用Ctrl+f查找報錯中提示的名字。

4.下標(biāo)越界錯誤
圖20.4示例了下標(biāo)越界錯誤,報錯為“l(fā)ist index out of range”(列表索引號超出范圍),程序在第1行定義了含有三個元素的列表arr,在第2行試圖訪問數(shù)組的第4個元素(索引號為3),數(shù)組元素索引號從0開始,范圍是0,1,2,本例中訪問的索引號超過了列表的最大索引號。

5.其它問題
程序可能出現(xiàn)的錯誤非常多,無法一一列舉,而報錯信息一般都是英文的,建議使用以下步驟查找錯誤原因:
首先,查看錯誤提示中的行號,以及行號之前的程序,看是否能發(fā)現(xiàn)代碼錯誤。
如果未找到原因,查看是否為以上幾種錯誤類型。
如果不是以上問題,用翻譯工具翻譯錯誤提示,以定位問題。
如果仍不能找到問題,在搜索引擎(如百度)中搜索錯誤提示,查看他人解決此類問題的方法。
20.2.2 調(diào)試方法
在程序中間加入斷點和打印信息都是標(biāo)準(zhǔn)的程序調(diào)試方法,特別是在運行他人編寫的代碼時,用這種方法可以了解每個階段做了什么。
我們寫的程序都相對簡單,類和函數(shù)并不多,當(dāng)運行別人代碼時,梳理互相調(diào)用關(guān)系很重要,最好能畫出簡單的調(diào)用關(guān)系圖。
1.print 函數(shù)
Python中的print函數(shù)用于打印輸出,它雖然不是專用的程序調(diào)試工具,但是如果養(yǎng)成使用print跟蹤程序運行的習(xí)慣,至少一半的問題都可以解決。
學(xué)習(xí)使用print調(diào)試程序不是學(xué)習(xí)函數(shù)的使用方法,而是在編寫程序的過程中,要理解程序每一行做了什么工作,每一行程序執(zhí)行之后當(dāng)前環(huán)境中各個變量的狀態(tài)是什么,尤其是在循環(huán)這樣較為復(fù)雜的結(jié)構(gòu)中,需要了解每一次循環(huán)中數(shù)據(jù)的變化。而使用print語句則是在開發(fā)者不太清楚當(dāng)前狀態(tài)的情況下,用程序輸出所關(guān)注的變量或者數(shù)據(jù)。
比如加載數(shù)據(jù)表文件之后,可以用print語句顯示加載后的內(nèi)容以及數(shù)據(jù)格式,又如跟蹤循環(huán)中數(shù)據(jù)的變化情況。如下例所示。
01 s = 0
02 for i in [1,2,3]:
03 s = s + i * 7
04 print(s, i)
其中第四行用于顯示每一次循環(huán)中的s值和i值。當(dāng)程序并未報錯,但輸出結(jié)果與想象中不同時,可以使用print方法跟蹤程序中數(shù)據(jù)的變化,以定位出錯的位置。
還有一種常用的調(diào)試方法是注釋掉可能錯誤的程序段,然后運行程序,如果運行仍不正常,說明不是被注釋掉語句的問題,繼續(xù)注釋更多的語句;如果注釋后運行正常,則說明是該段問題,然后嘗試注釋掉較少的語句,直到定位到具體出錯的行。
2. 調(diào)試工具pdb
pdb是Python的調(diào)試工具,由于pdb使用方法比較復(fù)雜,此處講解pdb只作為知識擴(kuò)展,不要求讀者掌握。
在Jupyter中加入魔法命令%pdb,即可在程序出錯時調(diào)用pdb,以便調(diào)試出錯時的具體代碼。例如,運行以下程序:
01 %pdb
02 arr = ['a','b','c']
03 print(arr[3])
程序運行到第三行時,會報錯,但并未退出,界面上將出現(xiàn)可交互的輸入框,開發(fā)者可以在輸入框中輸入程序如:print(arr),來進(jìn)一步運行程序。請注意:調(diào)試完成之后需要在輸入框中輸入exit退出調(diào)試模式。
使用pdb的另一種方法是在程序中加入斷點,當(dāng)程序運行到該行,會跳出可交互的輸入框,開發(fā)者可以通過pdb命令查看當(dāng)前狀態(tài),或者逐步執(zhí)行斷點之后的程序。
設(shè)置斷點的方法是,在程序中加入pdb.set_trace(),如以下程序所示:
01 import pdb
02 arr = ['a','b','c']
03 pdb.set_trace()
04 print(arr[3])
pdb常用的調(diào)式命令如下:
- 單步調(diào)試(進(jìn)入函數(shù)):s(tep)。
- 單步調(diào)試(不進(jìn)入函數(shù)):n(ext)。
- 繼續(xù)往后執(zhí)行,直到下個斷點:c(ont(inue))。
- 運行到函數(shù)結(jié)束:r(eturn)。
- 運行到當(dāng)前循環(huán)結(jié)束:unt(il)。
- 設(shè)置斷點:b(reak) 文件名:行號(或行號,或函數(shù)名)。
- 顯示當(dāng)前調(diào)用關(guān)系:w(here)。
- 顯示當(dāng)前代碼段:l(ist)。
- 顯示變量:p(rint) 變量名。
- 顯示當(dāng)前函數(shù)的參數(shù):a(rgs)。
- 顯示幫助信息:h(elp)。
- 退出:q(uit)。
20.2.3 錯誤處理
程序有時可能出現(xiàn)一些難以預(yù)料的錯誤,比如寫客戶端程序與服務(wù)器端交互,就可能遇到很多問題,如網(wǎng)絡(luò)未連接,對三方庫調(diào)用方法不對,與服務(wù)器數(shù)據(jù)交互格式不對,或者以前能正常運行,后來服務(wù)端修改了端口,找不到對應(yīng)功能等等。
這種情況下,程序員,尤其是寫程序供他人調(diào)用的程序員,需要識別出程序中可能出現(xiàn)的錯誤,進(jìn)行錯誤處理后,保證程序正常運行,而不會意外退出。
當(dāng)程序運行出錯時會拋出異常,如果不做處理,則程序會異常退出,Python用try/except方式捕獲異常,其語法規(guī)則如下:
01 try:
02 程序代碼
03 except <異常類> as <變量>:
04 異常處理代碼
05 else:
06 異常以外其它情況處理
07 finally:
08 無論是否異常,最終都要執(zhí)行的代碼
簡單實例如下:以讀方式打開文件test.txt,該文件不存在時將拋出異常,例程中捕獲異常,并顯示出具體的異常信息。
01 try:
02 f = open('test.txt', 'r')
03 except Exception as e:
04 print('error', e)
05 print('aaaa')
06 # 返回結(jié)果:
07 # error [Errno 2] No such file or directory: 'test.txt'
08 # aaaa
從返回結(jié)果可以看到,捕捉到異常信息后,程序正常執(zhí)行了之后的打印信息操作,而并未因為異常而崩潰。
課后練習(xí):(練習(xí)答案見本講最后的小結(jié)部分)
練習(xí)一:嘗試捕獲越界的錯誤。
20.3 思維訓(xùn)練
20.3.1 學(xué)習(xí)編程的好處
如果讀者從頭到尾學(xué)習(xí)了所有例程,并做完了所有習(xí)題,可能發(fā)現(xiàn)以下變化:
- 熟悉了程序、界面、網(wǎng)絡(luò)、數(shù)據(jù)庫、數(shù)據(jù)分析等概念。
- 熟練使用Python編程環(huán)境。
- 對大多數(shù)問題都能定位到關(guān)鍵點。
- 寫程序從修改變成了構(gòu)建。
- 不再抵觸較長的程序段。
- 變量命名更有章法。
- 更喜歡使用工具和快捷鍵。
- 能解決大多數(shù)語法方面的問題,至少有了解決思路。
……
12歲以下的學(xué)生,對于文中絕大多數(shù)的概念都是第一次接觸,即使當(dāng)時學(xué)會了,過一段時間也會淡忘,因此推薦整體看三遍以上,最終目標(biāo)是能自如地編寫練習(xí)中的每一個程序,并且能夠?qū)⒊绦蛴糜谌粘5臄?shù)據(jù)處理。
20.3.2 防患于未然
之前提到學(xué)習(xí)常常分為兩部分:一部分從經(jīng)驗中學(xué)習(xí),即構(gòu)建框架;另一部分從教訓(xùn)中學(xué)習(xí)。經(jīng)驗可以通過實踐或者學(xué)習(xí)規(guī)則構(gòu)造,而教訓(xùn)更多往往是自己犯了錯誤印象才深。
教訓(xùn)又可再細(xì)分成兩種情況:改正錯誤和防患于未然。發(fā)現(xiàn)問題及時改正可能導(dǎo)致處理過程的暫停和回退;而防患于未然則更多地源于之前的積累,它們在錯誤的行為之前就進(jìn)行了阻止,而從表面看來,似乎非常平穩(wěn),未經(jīng)波折。實際上是經(jīng)驗在毫無意識的情況下就發(fā)生了作用。比如我們不會在公共場景提到禁忌話題。
因此,很多時候看起來運氣比較好或者天生的靈感,實際上下意識的行為是源于之前的積累。對于一個嚴(yán)謹(jǐn)?shù)娜?,很多思路在沒有外顯時可能就已經(jīng)被過濾掉了,往往也顯得沒有幽默感,缺少想象力。嚴(yán)謹(jǐn)或者隨意可能取決于個人經(jīng)歷和環(huán)境,也可能是遺傳或者性格所致。
人們不能要求小孩子對陌生人又友好,又有戒心,但是有些成年人卻可以做到。我們在成長過程中不斷打磨,逐漸學(xué)會了在兩極之間取折中的方案,在不同的情況下使用不同的框架。
20.3.3 注意事項
最后,整理了一些學(xué)習(xí)編程中的注意事項,與大家共同學(xué)習(xí):
- 任何老師都不可能列出所有可能出現(xiàn)的問題,要學(xué)會在試錯中進(jìn)步。
- 基礎(chǔ)知識非常重要,無論文科理科記和背都是必須的,對常用知識構(gòu)建條件反射,才能有更多的腦力用于后續(xù)的學(xué)習(xí)和思考。
- 完成功能后整理代碼:從變量命名到代碼順序,都請嚴(yán)格要求自己。
- 量的問題積累太多,就變成了質(zhì)的問題:一個小問題,努力一下也許能解決,但是十個小問題堆疊起來,腦子就直接轉(zhuǎn)不動了。
- 代碼重構(gòu)比代碼填空難度大得多,盡量多練習(xí)構(gòu)建整體代碼邏輯。
- 分類和對比非常重要,如果不能直接找到答案,可以尋找類似的情況及處理方法。
- 代碼需要逐行讀懂,長代碼,很可能讀到后面忘了前面,建議邊讀邊記,最好能繪制流程圖,有時候也需要反復(fù)閱讀。
- 初學(xué)者對例程往往知其然,不知其所以然,在看他人代碼時盡量動手跟蹤調(diào)試程序。
- 寫程序像彈琴一樣需要不斷練習(xí),即使學(xué)一遍就會,后邊不用也會很快忘記。
20.4 小結(jié)
20.4.1 單詞
本講需要掌握的英文單詞如表20.1所示。

20.4.2 習(xí)題答案
- 練習(xí)一:嘗試捕獲越界的錯誤。
01 try:
02 arr=['a','b','c']
03 print(arr[3])
04 except Exception as e:
05 print('error',e)
06 print('aaaa')