刚学习 Python 程式设计的人常常会在执行出状况时不知所措, 这主要是因为 Python 直译器会喷出一大堆讯息, 但却是英文的讯息, 虽然大家都学过英文, 可是程式语言里的英文单字有些并不是一般英文常出现的, 而有些语句也像是法律条文为求精准简洁, 并不是那么简明易懂, 本文就希望能带大家习惯阅读错误讯息, 通常只要看懂错误讯息, 解决方法就自然浮现了。

语法错误

初学者最常见的错误是语法错误 (syntax error), 这指的是没有依照 Python 的语法规则写程式, Python 看不懂无法执行, 比较常见的是少了对应的引号或是括号,forwhile等复合叙述少了冒号等等。

互动环境下的语法错讯息

我们先以在 Python 互动环境下少打了字串的结尾引号为例 (不同版本的 Python 显示的讯息可能会不同, 本文以 3.10.2 为主要执行环境):

>>> print("hello)
  File "<stdin>", line 1
    print("hello)
          ^
SyntaxError: unterminated string literal (detected at line 1)
>>>

进入全屏模式 退出全屏模式

错误讯息内包含两个部分:分别是发生错误的位置以及错误的类型与说明。我们先以刚刚的例子说明第一部份:

  File "<stdin>", line 1
    print("hello)
          ^

进入全屏模式 退出全屏模式

其中第 1 列会告诉你是在哪一个档案里的第几列程式发生错误:

  File "<stdin>", line 1
       ---------  ------
           ^         ^
           |         |
      發生錯誤的檔案    -- 檔案裡的哪一列發生錯誤

进入全屏模式 退出全屏模式

当我们在 Python 互动环境下测试程式时, 档案名称会是 <stdin>, stdin 代表标准输入设备, 就是你的键盘, 表示是透过键盘直接输入程式让 Python 互动环境执行。

接着第 2 列会显示发生错误的那一列程式, 以及是在这一列的哪一个位置发生错误:

    print("hello)
          ^

进入全屏模式 退出全屏模式

你会看到程式的内容, 而且它会用 '^' 符号指出错误的位置, 像是本例就指出在字串开头这边出错了。如果是简单的错误, 看到这里大概就知道问题, 不过看不出来也没关系, 再看错误讯息的第二部分, 就可以知道错误的细节。

错误讯息的第二部分分成错误的类型与说明, 一样以刚刚的例子来解释:

SyntaxError: unterminated string literal (detected at line 1)
-----------  ------------------------------------------------
     ^                             ^
     |                             |
  錯誤類型                     錯誤的詳細說明

进入全屏模式 退出全屏模式

在本例中, 错误的类型是 SyntaxError, 直译就是『语法错误』, 表示没有依照 Python 的规定写程式, 所以 Python 读取程式内容后无法执行该列程式。

在错误的说明中, 它告诉我们遇到未结尾 (unterminated) 的字串, literal 这个字在程式设计上指的是直接以文字书写出资料的内容, 像是在程式中直接写明整数 23、浮点数 5.36、字串 "hello" 等等这些都叫做 literal。因此, 整个说明告诉我们的就是程式中书写的字串没有结尾, 由于 Python 规定书写字串内容时必须以一对英文引号包起来, 对照错误讯息的第一部分指出的错误位置, 就可以发现是 hello 之后少打了对应结尾的英文双引号, 只要补上就可以了。此外, 说明的最后还很贴心地再次告诉你这个错误是在程式的第一列发现 (detected) 的。

要特别说明的是, 错误讯息指出的错误位置未必就是少打字的地方, 但一定就是在附近, 只要对照错误的说明, 就可以找到真正错误的地方。

执行完整程式档时的语法错误讯息

如果你是执行程式档, 例如我们把刚刚的例子写在 test.py 档中:

❯ type test.py
print("hello)

进入全屏模式 退出全屏模式

然后在终端机中透过命令列执行:

❯ py test.py
  File "D:\code\python\test.py", line 1
    print("hello)
          ^
SyntaxError: unterminated string literal (detected at line 1)

❯

进入全屏模式 退出全屏模式

就会看到一模一样的错误讯息, 唯一的差别就是错误位置是以实际的程式档名标示, 而不是代表键盘输入的 "stdin"。

在 Visual Studio Code 等开发工具中执行程式的语法错误讯息

很多人是使用开发工具写程式, 这里就以 Visual Studio Code 为例, 我们开启刚刚的 test.py 档执行, 就会在下半部开启终端机窗格以命令列执行, 所以会看到一模一样的错误讯息:

VSCode 错误讯息

其实在开发工具中撰写程式的同时, 就已经会针对语法错误提出警告, 以 Visual Studio Code 安装的 Pylance 延伸模组为例, 它会在侦测到语法错误的地方表示红色的波浪线, 只要将滑鼠移到波浪线处, 就会出现提示讯息:

Pylance 语法错误

像是上图就告诉你这里书写的字串没有结尾。

另外, Pylance 也会在下方的问题窗格显示目前侦测到的所有语法错误:

Pylane 语法检查

像是上图就显示有两个语法错误, 只要点选错误讯息, 编辑窗格中就会标示错误的地方。本例中第一个错误是:

"(" was not closed

进入全屏模式 退出全屏模式

表示 "(" 没有关闭 (closing), 意思就是没有对应的 ")"。

第二个错误告诉我们书写的字串没有结尾:

String literal is unterminated

进入全屏模式 退出全屏模式

这个错误也导致了第一个错误, 因为字串没有结尾, 所以原本对应 "(" 的 ")" 被当成字串的内容, "(" 就没有对应的 ")" 了。

在 Colab 或是 Jupyter 中的语法错误讯息

如果你是在 Colab 中执行程式, 会看到如下的错误讯息:

Colab 语法错误

你可以看到错误讯息的格式和前面介绍的类似, 但是有两点不同:

  1. 在 Colab 中是以储存格为单位, 所以在 'File' 后面是 Colab 帮这个储存格编的识别名称, 标示的列号也是该储存格内的列号:
文件“<ipython-input-1-2fadf01e7f7a>”,第 1 行

识别名称中 "ipython-input-" 后面的数字是储存格的执行序号, 像是上例中就表示是在执行顺序 1 号的储存格出错。

  1. 由于我测试时 Colab 的 Python 版本为 3.7.12, 不同版本的 Python 显示的错误讯息可能会有差异, 像是这里标示的错误位置是在右括号之后:
打印(“你好)
^
  1. 错误类型虽然一样, 但是错误讯息是『在扫视 (scanning) 书写的字串内容时遇到一列的结尾 (EOL 是 End Of Line)』, 表示没有看到字串结尾的引号这一列就结束了:
SyntaxError:扫描字符串文字时 EOL

因此, 错误的位置是标示在这一列程式的结尾处。

  1. Colab 还很贴内心的在错误讯息后提供 StackOverflow 连结的按钮, 方便你到该网站寻求解答。

Jupyter (IPython) 中的语法错误讯息

如果是在 Jupyter (IPython) 中执行程式, 看到的错误讯息格式和 Colab 很类似:

Jupyter 语法错误

但是在标示错误位置时会直接以储存格的执行序号代替 Colab 中比较冗长且看起来复杂的识别名称:

  Input In [1]
    print("hello)
          ^

进入全屏模式 退出全屏模式

但是没有储存格内的列编号, 只能在错误说明结尾处看到列编号:

SyntaxError: unterminated string literal (detected at line 1)

进入全屏模式 退出全屏模式

由于这个 Jupyter 环境是以 Python 3.10.2 运行, 所以看到的错误讯息和互动环境下是一样的。

执行时期错误 (runtime error)

对比于语法错误是还没执行就发现程式写法不合规定, 执行时期错误则是执行程式后才发生的错误。

互动环境下的执行时期错误讯息

我们以一个简单的例子来说明执行时期错误:

>>> a = 20
>>> print(a[2])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not subscriptable
>>>

进入全屏模式 退出全屏模式

在这个例子里, 因为我们把整数当成像是串列这样的物件使用, 想要取得索引 2 的项目而出错。错误讯息和语法错误时一样, 分成错误位置与错误类型及说明两部分, 我们先来看第二个部分:

TypeError: 'int' object is not subscriptable

进入全屏模式 退出全屏模式

它告诉我们这是TypeError, 也就是型别错误, 意思就是这里不能用这种类型的资料, 后面的说明就明确解释 int 物件不能以索引取值 (subscriptable), 也就是不能搭配[]运算器使用。了解这些后, 只要找到出错的那一列程式, 看到[]运算器, 就可以发现运算元a是整数, 不能使用[]运算器, 修正就可以解决问题。

在标示错误位置的地方, 除了原本标示档案名称与列编号以外, 一开始的地方多了一列开头是 "Traceback" 的讯息, 它会带你往回追溯 (traceback) 导致错误发生的执行过程:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>

进入全屏模式 退出全屏模式

不过这个例子只有一列程式, 看不出回溯历程的意思, 我们改以底下的例子来说明:

>>> def f(a):
...     return a[2]
...
>>> print(f(20))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
TypeError: 'int' object is not subscriptable
>>>

进入全屏模式 退出全屏模式

你会看到回溯历程有两列, 第一列如下:

  File "<stdin>", line 1, in <module>

进入全屏模式 退出全屏模式

这告诉我们在程式的第一列出错了, 后面 "in <module>" 的意思是这一列程式并不在任何的类别或函式中, 而是在该程式档的最外层。由于出错的这一列程式是叫用f()函式, 错误是发生在该函式中, 因此要再看回溯历程的下一列才会知道真正出错的地方:

  File "<stdin>", line 2, in f

进入全屏模式 退出全屏模式

它告诉我们在f()函式的第二列发生了型别错误, 只要依据错误说明找出这一列哪里出错即可。

要注意的是回溯历程是依照先后次序 (most recent call last) 排列, 像是刚刚的例子, 程式是先执行:

>>> print(f(20))

进入全屏模式 退出全屏模式

然后才进到f()函式内的第 2 列:

>>> def f(a):
...     return a[2]
...

进入全屏模式 退出全屏模式

所以在回溯历程中也是依照这样的顺序排列。

完整程式档的执行时期错误讯息

如果是执行完整的程式档, 例如:

❯ type test.py
def f(a):
    return a[2]

print(f(20))

进入全屏模式 退出全屏模式

看到的错误讯息如下:

❯ py test.py
Traceback (most recent call last):
  File "D:\code\python\test.py", line 4, in <module>
    print(f(20))
  File "D:\code\python\test.py", line 2, in f
    return a[2]
TypeError: 'int' object is not subscriptable
❯

进入全屏模式 退出全屏模式

跟在互动环境下看到的差别在于:

  • 和语法错误一样, 是以真正的档案名称标示。

  • 程式的列号是以程式档内的整体编号顺序。

  • 除了标示档案名称与列号, 也会把出错的那一列程式列出来, 方便查看程式内容。

在 Colab 中的执行时期错误

如果是在 Colab 中执行一样的程式, 看到的错误讯息如下:

Colab 执行时期错误

看到的讯息很类似, 但是会列出出错处邻近的程式供参考, 也会明确指出出错的程式是哪一列:

---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-5-516efd866f6a> in <module>()
      2     return a[2]
      3 
----> 4 print(f(20))

<ipython-input-5-516efd866f6a> in f(a)
      1 def f(a):
----> 2     return a[2]
      3 
      4 print(f(20))

TypeError: 'int' object is not subscriptable

进入全屏模式 退出全屏模式

如果实际出错的地方是在不同的储存格, 也可以藉由识别名称中的执行序号找到出错的储存格, 例如若在新的储存格叫用f():

Colab 执行时期错误

你可以清楚的看到执行序号 6 的储存格叫用了执行序号 5 的储存格内的f()而出错:

---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-6-925a89c490fa> in <module>()
----> 1 print(f(200))

<ipython-input-5-516efd866f6a> in f(a)
      1 def f(a):
----> 2     return a[2]
      3 
      4 print(f(20))

TypeError: 'int' object is not subscriptable

进入全屏模式 退出全屏模式

Jupyter 中的执行时期错误

Jupyter 的标示方式就跟 Colab 很类似, 不过和语法错误不同的是, 执行时期错误会显示列号:

Jupyter 执行时期错误

它还非常贴心的进一步以不同颜色标示出在出错的那一列程式里出错的地方。

如果出错的地方在不同的储存格, 标示的方式也是一样:

Jupyter 执行时期错误

可以看出来是执行序号 4 的储存格叫用了执行序号 2 的储存格内的f()而出错。

叫用其他模组时出错

有了以上的基础, 现在遇到错误讯息就可以冷静对待, 例如使用 Python 提供的模组却出错:

>>> import os.path
>>> os.path.getatime("xxx.txt")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\Program Files\Python310\lib\genericpath.py", line 60, in getatime
    return os.stat(filename).st_atime
FileNotFoundError: [WinError 2] 系統找不到指定的檔案。: 'xxx.txt'
>>>

进入全屏模式 退出全屏模式

这里我们想要取得特定档案最后的存取时间, 从回溯历程可以知道实际上是在叫用os.stat()时出错, 不过在 Windows 上这个错误的讯息是中文的, 所以一看就懂。相同的程式在 Linux 上是英文:

>>> import os.path
>>> os.path.getatime("xxx.txt")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.9/genericpath.py", line 60, in getatime
    return os.stat(filename).st_atime
FileNotFoundError: [Errno 2] No such file or directory: 'xxx.txt'
>>>

进入全屏模式 退出全屏模式

不过只要认真读一下, 就知道问题出在指定的档案并不存在。阅读错误讯息还有一个好处, 就是可以知道实作的细节, 像是在这个例子哩, 我们可以知道os.path.getatime()是透过os.stat()达成, 如果想知道更进一步的细节, 我们也知道原始档在哪里, 只要找到 genericpath.py 内的第 60 列, 就可以看到实作内容:

    57
    58  def getatime(filename):
    59      """Return the last access time of a file,reported by os.stat()."""
    60      return os.stat(filename).st_atime
    61

进入全屏模式 退出全屏模式

出错还可以学东西, 是不是很赚?

小结

看到这里, 我们就具备了基本功, 剩下的就是看到各种错误讯息时, 能不能看懂其中的关键词汇?例如底下的错误:

>>> for i in 20:
...     print(i)
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>>

进入全屏模式 退出全屏模式

错误讯息说int物件不是iterable, 那么什么是iterable吗?我们不可能在这篇文章中把所有的错误讯息都讲解一遍, 得靠自己查阅文件, 这样的基本功还是不可避免的。

Logo

学AI,认准AI Studio!GPU算力,限时免费领,邀请好友解锁更多惊喜福利 >>>

更多推荐