官术网_书友最值得收藏!

在面對錯誤信息中的一大段技術性文本時,很多程序員會下意識地選擇忽略。但程序出錯的原因就在其中,我們需要用以下兩個步驟找到它:檢查回溯信息1,以及在網(wǎng)上搜索錯誤信息。

1 traceback是一個內(nèi)置模塊。如果程序報錯,那么traceback模塊會提供異常發(fā)生時的程序棧。——譯者注

如果代碼拋出的異常未被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)上搜索錯誤消息,沒準兒會更快地找到解決問題的關鍵線索。

錯誤信息通常短得根本構不成一個完整的句子。因為對程序員而言很常見,所以它們是作為提示信息而非完整的解釋出現(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”可以避免包括代碼中的特有部分。

如果通過這些搜索沒能得到有價值的結果,那么可以嘗試搜索完整的錯誤信息。

主站蜘蛛池模板: 郎溪县| 云龙县| 临夏县| 安丘市| 三门县| 乐安县| 海晏县| 常州市| 浦江县| 西畴县| 淄博市| 利津县| 宁阳县| 陕西省| 无锡市| 金平| 海城市| 神池县| 林州市| 满洲里市| 罗城| 慈利县| 原平市| 麦盖提县| 静乐县| 璧山县| 进贤县| 隆子县| 通山县| 文水县| 江北区| 和平区| 安阳县| 林周县| 应用必备| 太仓市| 香港 | 岱山县| 凤冈县| 龙江县| 阳江市|