- Python編程輕松進階
- (美)阿爾·斯維加特
- 2136字
- 2022-08-01 11:40:41
1.1 如何理解Python錯誤信息
在面對錯誤信息中的一大段技術性文本時,很多程序員會下意識地選擇忽略。但程序出錯的原因就在其中,我們需要用以下兩個步驟找到它:檢查回溯信息1,以及在網(wǎng)上搜索錯誤信息。
1 traceback是一個內(nèi)置模塊。如果程序報錯,那么traceback模塊會提供異常發(fā)生時的程序棧。——譯者注
1.1.1 檢查回溯信息
如果代碼拋出的異常未被except語句捕獲,那么Python程序就會崩潰。此時,Python會展示異常消息和回溯。回溯也被稱為調(diào)用棧,它展示了異常在程序中的具體位置,以及引發(fā)異常的函數(shù)調(diào)用鏈路。
我們來做個閱讀回溯信息的練習,輸入以下程序(有bug)并保存為abcTraceback.py。注意,行號僅起參考作用,不屬于程序的一部分。
1. def a(): 2. print('Start of a()') 3. b() # 調(diào)用b() ? 4. 5. def b(): 6. print('Start of b()') 7. c() # 調(diào)用c() ? 8. 9. def c(): 10. print('Start of c()') 11. 42 / 0 # 該行會導致以0為除數(shù)的錯誤 ? 12. 13. a() # 調(diào)用a()
這段程序中,函數(shù)a()調(diào)用了b()?,b()調(diào)用了c()?,調(diào)用關系是a()→b()→c()。在函數(shù)c()中,表達式42 / 0?引發(fā)了一個以0為除數(shù)的錯誤。該程序運行時的輸出是:
Start of a() Start of b() Start of c() Traceback (most recent call last): File "abcTraceback.py", line 13, in <module> a() # 調(diào)用a() File "abcTraceback.py", line 3, in a b() #調(diào)用b() File "abcTraceback.py", line 7, in b c() # 調(diào)用c() File "abcTraceback.py", line 11, in c 42 / 0 # 該行會導致以0為除數(shù)的錯誤 ZeroDivisionError: division by zero
讓我們從下面這行開始,逐行檢查信息:
Traceback (most recent call last):
這行信息說明接下來是回溯信息。“most recent call last”表明將按照調(diào)用順序列出所有函數(shù)調(diào)用,從第一個函數(shù)調(diào)用開始,到最后一個調(diào)用為止。
下一行展示了回溯的第一個函數(shù)調(diào)用:
File "abcTraceback.py", line 13, in <module> a() # 調(diào)用a()
這兩行是棧幀摘要,它展示了棧幀對象的內(nèi)部信息,其中包括函數(shù)調(diào)用時的局部變量、函數(shù)結束后返回的位置以及其他與函數(shù)調(diào)用相關的數(shù)據(jù)。棧幀對象隨著函數(shù)的調(diào)用而創(chuàng)建,直到函數(shù)返回時被銷毀。回溯顯示了導致崩潰的每一個函數(shù)調(diào)用棧幀的摘要信息。這次函數(shù)調(diào)用發(fā)生在abcTraceback.py文件的第13行,<module>表示這一行位于全局作用域。接下來是第13行的代碼,行首有兩個空格的縮進。
再往后4行分別是接下來兩個棧幀的摘要。
File "abcTraceback.py", line 3, in a b() # 調(diào)用b() File "abcTraceback.py", line 7, in b c() # 調(diào)用a()
我們可以發(fā)現(xiàn),嵌套在a()函數(shù)中的b()函數(shù)在第3行被調(diào)用,接著導致嵌套在b()函數(shù)中的c()函數(shù)在第7行被調(diào)用。注意,盡管在b()函數(shù)和c()函數(shù)調(diào)用之前,第2、6、10行的print()函數(shù)也被調(diào)用了,但它并未展示在回溯信息中,這是因為只有導致異常的函數(shù)調(diào)用的所在行才會被展示在回溯信息中。
最后一個棧幀摘要顯示了未被處理的異常的名字和信息。
File "abcTraceback.py", line 11, in c 42 / 0 # 該行會導致以0為除數(shù)的錯誤 ZeroDivisionError: division by zero
需要注意的是,回溯給出的行號是Python最終檢測到錯誤的地方,引起bug的罪魁禍首可能在這行之前。
錯誤信息像謎語一樣,是出了名地又短又難懂。在數(shù)學上,0不能作為除數(shù),這是一個常見的bug。你得知道這一點,否則“division by zero”(以0為除數(shù))這幾個字對你而言沒有任何意義。這個示例程序中的bug還不算很難查找。只要看看棧幀摘要指出的那行代碼,就能輕易地發(fā)現(xiàn)是表達式42 / 0引發(fā)了以0為除數(shù)的錯誤。
讓我們再看一個難一點的案例。輸入以下代碼,保存為zeroDivideTraceback.py:
def spam(number1, number2): return number1 / (number2 - 42) spam(101, 42)
運行時的輸出如下:
Traceback (most recent call last): File "zeroDivideTraceback.py", line 4, in <module> spam(101, 42) File "zeroDivideTraceback.py", line 2, in spam return number1 / (number2 - 42) ZeroDivisionError: division by zero
錯誤信息跟上一個示例相同,但從返回值number1 / (number2 - 42)很難直接看出以0為除數(shù)的錯誤。推斷過程是這樣的:由于/運算符的出現(xiàn)發(fā)生了除法運算,除數(shù),也就是表達式number2 - 42一定等于0了。結論顯而易見:一旦參數(shù)number2被設置為42,spam()函數(shù)就會失敗。
有時候,回溯信息可能指出錯誤出現(xiàn)在真正造成bug的行后的那行代碼。例如接下來的程序,第一行的print()函數(shù)調(diào)用語句缺少閉合的括號:
print('Hello.' print('How are you?')
但程序的錯誤信息指出問題是在第2行:
File "example.py", line 2 print('How are you?') ^ SyntaxError: invalid syntax
這是因為Python解釋器直到讀到第2行時才意識到存在語法錯誤。回溯可以表明程序從哪一行開始出問題,但這一行并不一定是罪魁禍首。如果棧幀摘要沒能提供足夠的信息排查出bug,又或者造成bug的罪魁禍首在回溯指出的位置前的某一行,那么只能退而求其次,即使用調(diào)試器逐行調(diào)試程序或者檢查日志信息,這可能會多花不少時間。另一種方式是在網(wǎng)上搜索錯誤消息,沒準兒會更快地找到解決問題的關鍵線索。
1.1.2 搜索錯誤信息
錯誤信息通常短得根本構不成一個完整的句子。因為對程序員而言很常見,所以它們是作為提示信息而非完整的解釋出現(xiàn)的。如果某個錯誤之前沒遇到過,那么你可以直接將其復制并粘貼到網(wǎng)上搜索,大概率會得到錯誤信息的具體含義及其可能的原因等詳細解釋。圖1-1顯示了搜索python“ZeroDivisionError: division by zero”的結果。這里有兩個技巧可以幫助精確查找:一是使用引號包裹錯誤信息作為關鍵詞;二是將python一起作為關鍵詞。

圖1-1 復制錯誤信息,將其粘貼到在線搜索工具中,可以快速得到相關解釋和解決方案
在網(wǎng)上搜索錯誤信息并不是耍小聰明,沒有人可以記住一門編程語言可能出現(xiàn)的所有錯誤信息。在網(wǎng)上搜索編程問題的答案對職業(yè)程序員而言是家常便飯。
你可能想去除代碼中特有的錯誤信息。來看下面這個例子:
>>> print(employeRecord) Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'employeRecord' is not defined ? >>> 42 - 'hello' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for -: 'int' and 'str' ?
這個例子中的變量employeRecord存在拼寫錯誤,導致了錯誤?。由于錯誤信息“NameError: name 'employeRecord' is not defined”中的employeRecord是你的代碼所特有的,因此最好搜索python “NameError: name”“is not defined”。
在最后一行,錯誤信息?中的int和str似乎分別指向42和hello這兩個值。所以將搜索詞縮減為python“TypeError: unsupported operand type(s) for”可以避免包括代碼中的特有部分。
如果通過這些搜索沒能得到有價值的結果,那么可以嘗試搜索完整的錯誤信息。
- OpenStack Cloud Computing Cookbook(Fourth Edition)
- AngularJS深度剖析與最佳實踐
- Bootstrap 4:Responsive Web Design
- FLL+WRO樂高機器人競賽教程:機械、巡線與PID
- HTML5與CSS3基礎教程(第8版)
- 學習OpenCV 4:基于Python的算法實戰(zhàn)
- 零基礎Java學習筆記
- R用戶Python學習指南:數(shù)據(jù)科學方法
- ArcPy and ArcGIS(Second Edition)
- 零基礎學Java(第5版)
- Java Web應用開發(fā)
- 前端程序員面試算法寶典
- Tkinter GUI Application Development Blueprints
- 計算機輔助設計與繪圖技術(AutoCAD 2014教程)(第三版)
- Python Natural Language Processing