【问题标题】:Which line is chosen to be reported in exception选择哪一行报异常
【发布时间】:2026-01-10 08:45:01
【问题描述】:

假设我在 Python 中有一条引发异常的多行代码。

Python 如何决定针对哪一行引发异常?

示例:(注意:我可以在每一行之后使用反斜杠 \

(1
 +0/0
 +3)

第 3 行出现异常(ZeroDivisionError 异常,+3))。

(1
 +
 0/0
 )

第 3 行引发异常。

(0/0
 +
 1)

第 2 行引发异常。

这个问题的灵感来自 this example 和 @Godman pointed out,异常不仅仅发生在最后一行(正如我之前所想的那样)。

【问题讨论】:

  • 你知道,当我对另一个问题发表评论时,我也在想同样的事情。
  • 在这种情况下,可能是其他一些非常本地化的原因(例如,.py 文件和 .pyc 文件之间可能存在差异)。
  • 通常是最后一行。如果源文件和实际运行的代码不同步,它可能是任何一行。您可以使用dis.dis() 查看与每个字节码指令相关的行号。
  • 它不会解释任何东西(包括函数),直到它到达结束括号...此时它会收到一个错误,所以当它收到错误时它在第 5 行..
  • @JoranBeasley:CPython 是 Python 语言的标准实现,很可能是您使用的那个。 :)

标签: python exception


【解决方案1】:

从根本上说,我不认为我们都在思考正确的路线。这里没有最后一行。解释器在完全接收到表达式时引发异常。根据 Python 语法:http://docs.python.org/reference/grammar.html,在您点击右大括号 ')' 之前,表达式并不完整。 Joran Beasley 在 cmets 中针对问题本身给出了相同的简要解释。

你可以做 3 件事来判断它的正确性,而不需要深入研究语法:-

  1. 在python解释器中编写这段代码:

    a=(1+2+0/0+4+5)

这也会引发 ZeroDivionError。

  1. 在python解释器中编写这段代码:

    a=(1+2+0/0+4+5 # 然后按回车

这会给你无效的语法,因为表达式不完整并且不能被解释器解析 PS:这个和问题中提到的代码是一样的

  1. 在python解释器中编写这段代码:

a = (1
+2
+0/0
+4
+5)

最终,直到您点击右大括号,表达式才会完成。因此,您可以继续在其中添加更多子表达式而不会出现任何异常。因此,从根本上说,解释器并不将这一切都视为行号。它一直等到所有表达式(包括子表达式)都完成。而且,它是一个适合interpeter的编程控制流程。

PS:请原谅我的答案格式。

新编辑:-

@Hayden:我认为通过不深入研究语法很容易解释其中的微妙之处。但是,供您参考,我只是从 URL 中复制代码:http://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html

运行步骤:- 1. 将问题中询问的代码写入 temp.py 文件并保存,然后将 temp 导入另一个文件或解释器中。这将创建 temp.pyc 2. 现在,将上述 URL 中的完整代码复制并粘贴到 byteCodeDetails.py 中,然后在命令提示符下运行该文件: python byteCodeDetails.py temp.pyc。函数 show_file 将在此处调用,并给出以下输出:-

magic 03f30d0a
moddate 458c2e50(2012 年 8 月 17 日星期五 23:54:05)代码
argcount 0
nlocals 0 stacksize 3 flags 0040 代码
640600640200640200151764030017640400175a000064050053 5
0
LOAD_CONST 6 (3)
3 LOAD_CONST 2 (0)
6 LOAD_CONST 2 (0)
9 BINARY_DIVIDE
10 BINARY_ADD
11 LOAD_CONST 3 (4)
14 BINARY_ADD
15 LOAD_CONST 4 (5)
18 BINARY_ADD
19 STORE_NAME 0 (a)
22 LOAD_CONST 5(无)
25 RETURN_VALUE
常量
1
2
0
4
5

3
名称 ('a',)
varnames ()
freevars ()
cellvars ()
文件名 'C:\Users\Python\temp1.py'

名称 ''
firstlineno 5
lnotab


所以,你可以注意到:-

  1. 引用上述链接: 在反汇编输出中,最左边的数字 (1, 2, 3) 是原始源文件中的行号,接下来的数字 (0, 3, 6, 9, ...) 是字节偏移量 同样,对于您的代码,最左边的数字只有 5,即行号,右边的列表示由编译器翻译的助记符(由解释器读取)您的代码。,从而指示表达式是如何形成的,并且它们的形成被编译代码中的行号的值所取代。
  2. firstlineno 指向 5。

现在,只需对 temp.py 文件中的初始代码稍作更改即可:-

a = (1
+2
+0/0
+4+
5)

现在,再次执行上述 2 个步骤。以下是输出:-

魔法 03f30d0a
moddate 0f8e2e50(2012 年 8 月 18 日星期六 00:01:43)
代码
argcount 0
nlocals 0
stacksize 3

标志 0040
代码 640600640200640200151764030017640400175a000064050053
4
0 LOAD_CONST 6 (3)
3 LOAD_CONST 2 (0)
6 LOAD_CONST 2 (0)
9 BINARY_DIVIDE
10 BINARY_ADD
11 LOAD_CONST 3 (4)
14 BINARY_ADD

5 15 LOAD_CONST 4 (5)
18 BINARY_ADD
19 STORE_NAME 0 (a)
22 LOAD_CONST 5(无)
25 RETURN_VALUE
常量
1
2
0
4
5

3
名称 ('a',)
varnames ()
freevars ()
cellvars ()
文件名 'C:\Users\Python\temp1.py'
名称 ''
firstlineno 4

lnotab 0f01

嗯,现在你可以清楚地看到两件事:-

  1. 字节码由下一行所示的两行组成,分别为'code 640600640200640200151764030017640400175a000064050053',前缀为'4'和'5'。这表明编译器已经解析了 .py 文件并将 temp.py 中的代码转换为 2 行代码,这些代码将由解释器运行。 注意这里第4行的内容无论表达式是否完整都会被解释器执行
  2. firstlineno 的值从 5 变为 4

这个冗长讨论的重点是,无论字节码向解释器表明这是一行的开始以及应该为此行执行的相应语句,解释器只运行该行和相应的旁边写着声明。

您问题中的代码将 firstlineno 显示为 5,这就是您在第 5 行收到错误的原因。希望这对现在有所帮助。

【讨论】:

  • 你是对的,从某种意义上说,“线”可以不被括号打断。但是我不认为这会“从根本上”改变任何东西,但我喜欢这个双关语。 :)
  • @Hayden:我不确定你是否同意这个答案
  • 我不同意,因为即使在您担心的情况下,(表达式的)最后一行的概念:如果表达式是使用括号编写的“最后一行”只是包含最终 ) 的那一行。 语法错误 似乎像其他异常一样显示最后一行...
  • @Hayden:我刚刚在答案中添加了更多解释,因为我无法在此评论中添加太多内容。前面的解释保持不变,只是进行了一些格式更改。请查看。
  • 谢谢,我已经用您的回答启发的一些新示例更新了这个问题,我没有意识到这不仅仅是最后一行!我无法理解/找到的一件事是firstlineno 是如何定义的,我认为这就是可以回答这个问题的一点。
【解决方案2】:

异常将指向包含的行*:

  1. 最后一个运算符(如果之前的文字/运算符导致异常)。

  2. 最后一个文字(否则即最后一个文字/运算符导致异常)。

.

然而,如果这不是您看到的行为,它可能是由您的一个 py(源)文件及其对应的(编译) pyc 文件或运行代码(在内存中)。以下是一个说明性示例。

  • 假设E.py 包含:

    def z():
        0/0
    
  • 从 python 命令行,import E(这会将E.py编译成字节码:E.pyc,并放入内存中)。

  • 调用E.z(),这将产生一个异常,在z 的第2 行,显示0/0 行——这并不奇怪。

  • 回到E.py源文件,在顶部插入两行,在第二行插入字符串"oh dear, oh dear"

  • 返回python命令行,再次调用E.z()

  • 异常(在第 2 行,in z)现在显示 "oh dear, oh dear"

*更新:我没有这方面的参考资料,如果你遇到了请发表评论。 我之前以为只是最后一行!

【讨论】:

  • .py.pyc 文件是否不同步并不重要。重要的是.py 文件是否与实际运行的代码(在内存中)同步。
  • 谢谢@SvenMarnach,我没有意识到这是在内存中。