18

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

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

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

(1
 +0/0
 +3)

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

(1
 +
 0/0
 )

在第 3 行引发异常。

(0/0
 +
 1)

在第 2 行引发异常。

这个问题受到了这个例子的启发,@Godman指出异常不仅仅发生在最后一行(正如我之前所想的那样)。

4

2 回答 2

4

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

您可以做 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)

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

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 将在此处调用,并给出以下输出:-

魔术03F30D0A MODDATE 458C2E50(周五8月17日 23:54:05
2012)代码
argcount 0 nlocals 0 stackSize 3 标志
0040代码
6406406402006402001517640176403001764030017640400175A00175A00 00 0000640500500; 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
consts
1
2
0
4
5

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

name ''
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(SAT 8月18日00:01:43 2012)
代码
ARGCOUNT 0 nlocals
0 stackSize 3 标志
0040

代码
640640200640200640200176440300176403001764040400175A00175A00 00 00 0064050050053 4
0
30(30
)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 (None)
25 RETURN_VALUE
consts
1
2
0
4
5
None
3 个
名称 ('a',)
varnames ()
freevars ()
cellvars ()
文件名 'C :\Users\Python\temp1.py'
name ''
firstlineno 4

lnotab 0f01

好吧,现在您可以清楚地看到两件事:-

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

这个冗长讨论的全部要点是,无论字节码向解释器表明这是一行的开始以及应该为该行执行的相应语句,解释器只运行该行并接下来编写相应的语句给它。

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

于 2012-08-17T11:52:09.430 回答
1

异常将指向包含以下任一项的行*

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

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

.

但是,如果这不是您看到的行为,则可能是由于您的 py(源)文件之一与其对应的(编译的)pyc 文件或正在运行的代码(在内存中)存在差异。以下是一个说明性示例。

  • 假设E.py包含:

    def z():
        0/0
    
  • 从 python 命令行,import E(这将编译E.py为 byte-code: E.pyc,并放入内存中)。

  • Call E.z(),这将产生一个异常,在第 2 行 in z,显示该行0/0- 这里不足为奇。

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

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

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

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

于 2012-08-17T11:05:25.317 回答