🗺️ 程式互動式地圖
零基礎起步・第 10 課

看懂錯誤訊息與除錯

一大段紅色英文跳出來,不代表你搞砸了,那是 Python 寫給你的事故報告:哪一行、做什麼事的時候、出了什麼問題。這一課教你從最後一行往上讀,看懂常見錯誤,並學會沒有錯誤訊息時怎麼找出算錯的地方。

從最後一行往上讀 錯誤在函式間往外傳 try/except 與除錯方法

💡 先搞懂問題

虛構的「北辰補習班」有一支小程式,替每位學生算平均分數、印成報表。前幾天都好好的,今天新來的學生阿哲還沒考過試,分數串列是空的,按下執行,畫面先印出小美的平均,接著跳出一大段紅字,最後的「報表完成」沒有出現。第一次看到這種畫面的人,常見的反應是整段跳過、直接去改程式碼,或是只看第一行的 Traceback (most recent call last),看不懂就放棄。

其實這段紅字是有固定格式的報告,叫 Traceback(追蹤記錄,也常直接叫錯誤訊息)。它的重點全在最後一行:冒號前是錯誤類型,冒號後是一句說明;往上每兩三行一組,告訴你出錯當下程式是從哪一行、經過哪個函式一路走到出事的地方。只要知道從哪裡開始讀,大部分錯誤訊息都會變得很好懂。

🖥️ 輸出畫面(Python 3.13)小美平均85.0Traceback(mostrecentcalllast):File"/tmp/b0_err/main.py",line8,in<module>report("阿哲",[])~~~~~~^^^^^^^^^^^^File"/tmp/b0_err/main.py",line5,inreportavg=average(scores)File"/tmp/b0_err/main.py",line3,inaveragereturntotal/len(scores)~~~~~~^~~~~~~~~~~~~ZeroDivisionError:divisionbyzero開頭:以下是出錯時的呼叫過程① 主程式第 8 行(最早)② report 函式第 5 行③ average 第 3 行:出事的地方先讀這行:錯誤類型+說明讀的順序:由下往上
北辰補習班報表程式的真實錯誤畫面(Python 3.13 實際執行,程式在實驗室一)。讀的順序是由下往上:先看最後一行知道「發生了除以 0」,再往上看是在 average 函式的第 3 行;再往上兩組說明它是被 report 的第 5 行呼叫、而 report 又是被主程式第 8 行呼叫的。~ 和 ^ 標出那一行裡出事的位置。

生活比喻:包裹的配送失敗通知單

你寄了一個包裹,幾天後收到一張配送失敗通知單。單子上由上往下列出包裹經過的每一站:台北物流中心收件、台中轉運站分揀、豐原配送站派送,最後一行用粗體寫著失敗原因:「查無此地址」。你不會從第一行開始慢慢研究台北物流中心,而是直接看最後一行的原因,再往上看是在哪一站失敗的。如果原因是地址寫錯,問題其實出在你寄件時填的單子,不是配送站做錯事;配送站只是第一個發現問題的地方。

回到程式:通知單就是 Traceback。單子上的每一站,對應 Traceback 裡的每一組 File "…", line 8, in <module>:哪個檔案、第幾行、在哪個函式裡(<module> 指的是最外層的主程式),下一行是那一行的程式碼。開頭那句 most recent call last 的意思是「最近的呼叫寫在最後」,所以越下面越接近出事的地方。最後一行的失敗原因,就是錯誤類型(exception type)和說明,例如 ZeroDivisionError: division by zero(除以零)。在 Python 裡,執行中發生的錯誤叫例外(exception),Python 說它被「拋出(raise)」了。
配送通知單(比喻)Traceback 的對應配送失敗通知單Traceback 錯誤追蹤依序列出經過的每一站每一層呼叫,最近的在最後站名+那一站的作業紀錄File、行號、函式名+那行程式最後一行的失敗原因分類錯誤類型 ZeroDivisionError原因說明「查無此地址」冒號後的說明 division by zero第一個發現問題的配送站最下面那組 File(出事的行)其實是寄件人填錯地址根源可能在上層(傳了空串列)
左欄是配送失敗通知單,右欄是 Traceback 的對應部分。最後一列最重要:錯誤訊息指出的是「第一個發現問題的地方」,真正要改的地方可能在更上層。

這個比喻有兩個地方和程式不同。第一,通知單上的每一站都已經做完自己的工作,Traceback 裡的每一層卻是「還在等待、被迫中止」的函式呼叫(第 9 課的呼叫框):主程式在第 8 行等 report,report 在第 5 行等 average,錯誤一發生,三層全部停下。第二,有一種錯誤像是包裹在寄件櫃台就被退回、根本沒出貨:程式寫法本身不合 Python 的文法(語法錯誤),Python 在開始執行前就會拒絕,一行都不會跑。還有一種錯誤連通知單都沒有:包裹準時送到了,打開卻發現寄錯東西,也就是程式順利跑完、答案卻是錯的。這三種情況,這一課都會一一處理。

🎮 互動實驗室一:錯誤從函式裡一層層往外傳

這是上面那張錯誤畫面的程式,用 Python 3.13 真的執行、逐步記錄下來。第一次呼叫 report 一切正常;第二次傳進空串列,錯誤在最裡層的 average 發生。請注意左欄的呼叫框:錯誤沒有被處理時,最上層(最新)的框會被迫中止、拿掉,錯誤交給下一層,直到主程式也停下。停在 🤔 的地方,先猜再按下一步。

畫面說明:第 17 步左欄疊著三個框(全域、report、average),和錯誤畫面裡的三組 File 一一對應。第 18 步發生錯誤,接下來每按一次,就有一個框被迫中止、拿掉,錯誤往外交一層。最後一步輸出畫面出現紅字,你會發現「報表完成」始終沒有印出:例外沒被處理,整支程式就停在那裡。

🎮 互動實驗室二:用 try/except 接住轉換失敗

請使用者輸入年齡,再用 int() 轉成整數(第 3 課),是最常見的出錯場景:input() 拿到的永遠是字串,使用者只要打「二十五」或「25.5」,轉換就失敗。右上角選單有四種情況:第一種沒有防護,後三種把可能出錯的那一行放進 try,並準備一段 except ValueError: 處理轉換失敗。比較它們的執行路線:綠色箭頭會跳過哪幾行。

畫面說明:「沒有 try」那一段在第 3 步發生 ValueError,第 4 步就是終點,第 3 行永遠沒有執行。切到「有 try,輸入二十五」,同樣在 int(text) 出錯,但下一步綠色箭頭跳到 except ValueError:,印出提示後繼續執行最後一行,程式正常結束。切到「輸入 25」,則是 except 底下兩行完全被跳過。age 這個名字只有在轉換成功時才會出現在記憶體裡。

🎮 互動實驗室三:錯誤診斷卡

12 張卡片,每張是一小段程式和它在 Python 3.13 執行後的真實畫面。照這一課的方法讀:先看最後一行的錯誤類型與說明,再找行號和 ^ 標出的位置,然後選出最合適的判斷或改法。其中有一張完全沒有錯誤訊息,要靠你自己發現答案不對。

得分 0 / 0
連續答對 0
畫面說明:每張卡先找三樣東西:最後一行的錯誤類型、行號、^ 指的位置。

📘 原理補完

1. 讀 Traceback 的四個步驟

  1. 看最後一行冒號前面:錯誤類型,告訴你是「哪一類」問題,例如名字不存在(NameError)、型別不對(TypeError)。
  2. 看最後一行冒號後面:說明,常常直接寫出是哪個名字、哪個值,例如 '摩卡'、'3.5';新版 Python 還會加上 Did you mean: …? 的建議。
  3. 往上找最下面那一組 File:檔名、第幾行、在哪個函式裡(in <module> 是主程式),下一行就是出事的程式碼,^ 和 ~ 標出位置。
  4. 再往上看呼叫過程:只有錯誤發生在函式裡時才會有好幾組;它們告訴你是誰把這個值傳進來的,根源常常在這裡。
Traceback(mostrecentcalllast):File"/tmp/b0_err/main.py",line2,in<module>print("你今年"+age+"歲")~~~~~~~~~^~~~~TypeError:canonlyconcatenatestr(not"int")tostr~ 參與這個動作的部分:"你今年" 和 age^ 真正出事的動作:第一個加號(字串 + 整數)最後一行:只能把字串接上字串,不能接 int
這是實驗室三其中一張卡的真實畫面。~ 和 ^ 是 Python 3.11 起加入的位置標記:^ 指出真正出事的那一個動作,這裡是加號;~ 標出參與這個動作的其他部分,也就是加號兩邊的 "你今年" 和 age。合起來讀就是:「字串加整數」這個加法做不了。標記不是每次都有,有些行 Python 不畫(例如實驗室一的 avg = average(scores)),這時就把整行一起看。

2. 三種錯誤:沒開始、跑一半、跑完卻錯

錯誤依照「什麼時候被發現」分成三種,處理方法完全不同。Python 執行 .py 檔時,會先把整個檔案讀一遍,確認每一行都符合 Python 的文法,這一關沒過就是語法錯誤(syntax error),一行都不會執行。文法沒問題,開始一行一行執行,執行到某一行做不下去(除以零、找不到名字),就拋出例外(exception),在那一行停下,這叫執行時錯誤。第三種是邏輯錯誤(logic error):每一行都順利執行完,但程式做的事和你想的不一樣,Python 不會給任何訊息。

語法錯誤SyntaxError/IndentationError第 1 行— 沒有執行第 2 行✖ 在這裡出錯第 3 行— 沒有執行→ 檢查階段就停下,一行都不執行執行時錯誤例外 exception第 1 行✓ 執行第 2 行✖ 在這裡出錯第 3 行— 沒有執行→ 執行到出錯的行停下,印出 Traceback邏輯錯誤沒有錯誤訊息第 1 行✓ 執行第 2 行✓ 執行第 3 行✓ 但結果錯→ 全部執行完,答案卻不對
同樣是三行 print 的程式。語法錯誤時,第 2 行少了右括號,Python 在檢查階段就停下,連第 1 行的「第一行」都不會印出;執行時錯誤會先執行前面的行,在出錯的那一行停下;邏輯錯誤則全部執行完,只是結果不對。
種類什麼時候發現前面的行有沒有執行畫面長怎樣例子
語法錯誤執行前的檢查階段沒有,一行都不執行沒有 Traceback 開頭,直接是 File、程式碼、標記、SyntaxError 或 IndentationError少冒號、括號沒關、縮排不一致
執行時錯誤(例外)執行到那一行時有,執行到出錯為止Traceback (most recent call last): 開頭,最後一行是錯誤類型與說明名字拼錯、字串加數字、除以零、索引超出範圍
邏輯錯誤你自己檢查結果時全部執行完沒有任何錯誤訊息,只是答案不對range 少一圈、條件寫反、用錯變數

因為語法錯誤發生在執行之前,播放器沒辦法逐行展示它,下面是 Python 3.13 實際的畫面。注意它們沒有 Traceback 那一行,因為程式根本還沒開始呼叫任何東西。File 後面的路徑是這支檔案在電腦上的位置(本頁的範例是在 Linux 的 /tmp 資料夾執行;Windows 上會像 C:\Users\你的名字\main.py)。

if 那一行少了冒號:Python 直接說 expected ':'(這裡應該要有冒號),^ 指在行尾。

price = 120
if price > 100
    print("滿百")
  File "/tmp/b0_err/main.py", line 2
    if price > 100
                  ^
SyntaxError: expected ':'

括號沒有關:錯誤指的是「左括號從來沒有關上」,位置在那個左括號。注意輸出畫面上連第 1 行的「第一行」都沒有出現。

print("第一行")
print("第二行"
print("第三行")
  File "/tmp/b0_err/main.py", line 2
    print("第二行"
         ^
SyntaxError: '(' was never closed

函式(或 if、for)底下沒有縮排:IndentationError 是語法錯誤的一種,專門指縮排的問題。說明裡連「第 1 行是函式定義」都告訴你了:

def greet(name):
print("歡迎", name)
  File "/tmp/b0_err/main.py", line 2
    print("歡迎", name)
    ^^^^^
IndentationError: expected an indented block after function definition on line 1

不該縮排的地方多了空白:unexpected indent,意思是「這裡不該縮排」。常見於從網頁複製程式時多帶了空白。

print("開始")
    print("多了空白")
  File "/tmp/b0_err/main.py", line 2
    print("多了空白")
IndentationError: unexpected indent

語法錯誤的位置有時會比真正的錯誤晚一點:例如上一行少了右括號,Python 可能要讀到下一行才確定出問題。看到語法錯誤時,除了標出的那一行,也要檢查它的上一行。

3. 常見的執行時錯誤一覽

下面右欄都是 Python 3.13 實際執行後的最後一行,程式碼和完整畫面在實驗室三的卡片裡。遇到不認識的錯誤類型,可以先查這張表,再到 官方的內建例外清單 找。

錯誤類型白話意思新手最常見的原因Python 3.13 的最後一行
NameError找不到這個名字拼錯字、大小寫不同、還沒指派就使用、用了函式裡的區域變數NameError: name 'totl_price' is not defined. Did you mean: 'total_price'?
TypeError這個型別不能這樣用字串加數字、對 None 做運算、呼叫函式時引數個數不對TypeError: can only concatenate str (not "int") to str
ValueError型別對,但值不合法int() 轉換有小數點或中文的字串ValueError: invalid literal for int() with base 10: '3.5'
IndexError索引超出範圍忘了索引從 0 開始,用 len() 當索引IndexError: list index out of range
KeyError字典裡沒有這個鍵鍵拼錯、資料裡本來就沒有這筆KeyError: '摩卡'
ZeroDivisionError除以零計數或長度剛好是 0,例如空串列的平均ZeroDivisionError: division by zero
AttributeError這種物件沒有這個方法或屬性方法名稱拼錯、把別的語言的寫法帶進來AttributeError: 'str' object has no attribute 'uper'. Did you mean: 'upper'?

表格裡有兩行出現 Did you mean:拼錯的名字或方法和某個存在的名字很像時,Python(3.10 起)會主動猜你想打的是哪一個。這個建議通常是對的,但它只是猜測,例如實驗室三的 cart.push() 就沒有任何建議,因為串列的方法裡沒有和 push 夠像的名字,你得自己知道串列加東西是用 append(第 5 課)。

4. 錯誤發生在函式裡:Traceback 有好幾層

第 9 課說過,函式呼叫函式時,呼叫框會疊起來。例外在最上層(最新)的框發生,如果那個函式沒有處理它,函式就被迫中止、框被拿掉,例外交給呼叫它的那一行;那一行如果也沒處理,就再往外交,一直到主程式。主程式也沒處理,整支程式停止,印出 Traceback。所以 Traceback 的每一組 File,就是錯誤經過的每一個框,順序由外到內。

全域(主程式)第 8 行 report("阿哲", [])被迫中止report 的呼叫框第 5 行 avg = average(scores)被迫中止average 的呼叫框第 3 行 return total / len(scores)✖ 除以 0程式停止,印出 TracebackTraceback 的三組 Fileline 8, in <module>line 5, in reportline 3, in averageZeroDivisionError
實驗室一第二次呼叫時的呼叫堆疊。紅色箭頭是例外往外傳的路線:average 第 3 行除以零,交給 report 第 5 行,再交給主程式第 8 行,三層都沒有 try/except,程式停止。右邊是對應的 Traceback 三組 File,順序和左邊由上往下的框一致。

讀多層 Traceback 時,最下面那組告訴你「哪個動作做不下去」,往上的幾組告訴你「這個不合理的值是從哪裡來的」。這個例子裡,除法本身沒寫錯,真正該決定的是:空串列要不要算平均?可以在 average 裡先檢查 len(scores) == 0,或在呼叫 report 之前就把沒有成績的學生排除。錯誤訊息指出的是第一個發現問題的地方,修在哪裡要由你判斷。

5. 沒有錯誤訊息,答案卻不對:邏輯錯誤與除錯方法

想算三次考試的總分,70 + 85 + 90 應該是 245,下面的程式卻印出 175,而且沒有任何紅字。這時沒有錯誤訊息可以讀,只能自己找。最有效的兩個方法都在下面這個播放器裡:一是逐行執行,看每一步名字貼到什麼;二是在可疑的地方加 print(),把迴圈每一圈的變數印出來,和你心裡預期的值比對。選單第二段就是加了 print 的版本,第三段是修正後的版本。

畫面說明:在「有 bug」那段,盯著 i 第一次貼上的值:它是 1,不是 0,所以 scores[0] 的 70 從來沒被加進去。加了 print 的版本會在輸出畫面印出每一圈的 i,第一行就是 i = 1,問題一眼就看得到。修正版把 range(1, len(scores)) 改成 range(len(scores))。另外你可能會發現,total 加完之後的箭頭有時指向串列格子裡的同一個數字物件(例如 85),修正版一開始 i 和 total 也指向同一個 0:這是 Python 重用了相同的不可變整數,因為它不能被修改,共用不會出問題。

播放器以外,你自己的電腦也能逐行執行:VS Code 等編輯器(見 編輯器與 IDE 節點)都有除錯器(debugger),在行號旁點一下設中斷點,程式就會停在那裡讓你看變數。不論用哪種工具,除錯的流程都差不多:

① 讀訊息、重現問題先讀最後一行,再跑一次確認② 縮小範圍少量資料、拆段、暫時註解③ 看實際的值逐行執行或 print 變數④ 修正並再跑一次用原本出錯的資料驗證還沒解決?再繞一圈
除錯是一個循環,不是一次猜中。重點在「縮小範圍」:把資料縮到最少(例如只留三個分數)、把程式拆成一段一段確認,或暫時用 # 把一半的程式註解掉,看問題還在不在,直到剩下幾行可疑的程式。修正後一定要用原本出錯的資料再跑一次。

把錯誤拿去搜尋或問 AI 時要附上什麼。直接貼「我的程式壞了」很難得到好答案。請附上:(1) 完整的 Traceback,從 Traceback 那一行到最後一行,不要只貼最後一行,也不要自己改寫;(2) 出錯的那幾行程式,加上它用到的變數是怎麼來的;(3) Python 版本(在終端機打 python --version)與執行環境(VS Code、Colab…);(4) 你預期的結果和實際的結果;(5) 你已經試過什麼。邏輯錯誤沒有 Traceback,第 (4) 點就特別重要。貼之前,把密碼、API 金鑰、客戶資料這類內容刪掉或換成假的。搜尋引擎則適合只貼最後一行,並把自己的變數名稱、檔案路徑拿掉,例如搜尋 TypeError: can only concatenate str (not "int") to str。

6. try/except 入門:預料得到的錯誤,先準備好處理方式

有些錯誤不是程式寫錯,而是外面的資料不照規矩來,例如使用者亂打、檔案內容有一筆壞掉。這種「預料得到、也知道怎麼處理」的錯誤,可以用 try/except 接住:把可能出錯的程式放在 try: 底下(要縮排),在 except 錯誤類型: 底下寫出錯時要做的事。try 裡某一行拋出例外,這一行和 try 裡剩下的行都不再執行,直接跳到類型相符的 except;沒有出錯,except 整段跳過。不論哪條路,之後都繼續往下執行。

1text=input("請輸入年齡:")2try:3age=int(text)4print("明年你就",age+1,"歲")5exceptValueError:6print("請輸入整數,例如25")7print("程式繼續執行")成功:第 4 行執行完,跳過 except,到第 7 行失敗:第 3 行拋出 ValueError → 第 4 行不執行,跳到第 5 行的 except兩條路最後都會執行第 7 行;except 底下的內容只有出錯時才執行
實驗室二的兩條路線。綠色是轉換成功:try 全部執行,跳過 except。紅色是轉換失敗:在 int(text) 那一行就離開 try,age 沒有被建立,下一行的 print 也不執行,改執行 except ValueError: 底下的提示。兩條路最後都走到「程式繼續執行」。

第一個完整範例是實務上很常見的寫法:用 while True 一直問,直到使用者輸入正確為止(while 和 break 是第 8 課的內容)。下面的輸出是模擬使用者依序輸入「二十五」「25.5」「25」時,Python 3.13 實際執行的畫面(直接執行時,你打的字會出現在提示後面;這裡輸入是從檔案讀進來的,所以畫面上看不到你打的字)。

# 一直詢問,直到使用者輸入整數為止
while True:
    text = input("請輸入年齡:")
    try:
        age = int(text)            # 可能拋出 ValueError 的那一行
        break                      # 上一行成功才會執行到這裡:跳出 while 迴圈
    except ValueError:             # 只接住「轉換失敗」這一種錯誤
        print("看不懂「" + text + "」,請輸入整數,例如 25")
print("明年你就", age + 1, "歲")
請輸入年齡:看不懂「二十五」,請輸入整數,例如 25
請輸入年齡:看不懂「25.5」,請輸入整數,例如 25
請輸入年齡:明年你就 26 歲

第二個範例處理一批資料:其中有幾筆壞掉,不希望整批停下來,就逐筆轉換,壞的那筆記下來、跳過:

# 一批從表單收到的訂購杯數,其中有幾筆填錯
raw = ["2", "1", "三", "4", "", "3"]
total = 0
bad = []                           # 記下轉換失敗的那幾筆
for t in raw:
    try:
        total = total + int(t)     # int("三")、int("") 都會拋出 ValueError
    except ValueError:
        bad.append(t)              # 這一筆跳過,繼續處理下一筆
print("有效杯數合計:", total)
print("無法辨識:", bad)
有效杯數合計: 10
無法辨識: ['三', '']
不要用 try 把錯誤藏起來。初學者常寫 except:(不寫錯誤類型)或 except Exception: 加一行 pass,讓紅字消失。這樣程式看起來不會壞,但名字拼錯、型別用錯這類真正的 bug 也一起被吞掉,結果默默算錯,比當場報錯更難找。原則是:只接你預料得到、而且知道怎麼處理的錯誤類型,其他錯誤就讓它照常顯示。try/except 的進階用法(else、finally、自己拋出例外)在 例外處理 節點。

7. 以後會在哪裡遇到

讀 AI 或資料分析的範例程式時,最常見的錯誤其實就是這一課的這幾種:資料表裡沒有那個欄位名稱是 KeyError,把文字欄位拿去計算是 TypeError 或 ValueError,模型輸入的形狀不對時,套件也會在最後一行寫出期待的形狀和實際的形狀。套件的 Traceback 往往很長,中間有很多層是套件自己的程式碼,讀法還是一樣:先看最後一行,再往上找第一組你自己的檔案,那通常就是該修改的地方。

✅ 自我檢測

6 題原創的程式閱讀題,所有答案都用 Python 3.13 實際執行確認過;輸出有好幾行時,選項裡用 ⏎ 表示換行。選完會立即顯示對錯與解析,全部作答後出現總分。目前得分:0 / 6