except 子句中的断点无法访问绑定的异常
breakpoint in except clause doesn't have access to the bound exception
考虑以下示例:
try:
raise ValueError('test')
except ValueError as err:
breakpoint() # at this point in the debugger, name 'err' is not defined
此处,输入breakpoint后,调试器无权访问绑定到err
的异常实例:
$ python test.py
--Return--
> test.py(4)<module>()->None
-> breakpoint()
(Pdb) p err
*** NameError: name 'err' is not defined
为什么会这样?如何访问异常实例?目前我正在使用以下解决方法,但感觉很尴尬:
try:
raise ValueError('test')
except ValueError as err:
def _tmp():
breakpoint()
_tmp()
# (lambda: breakpoint())() # or this one alternatively
有趣的是,使用这个版本,我还可以在调试器中向上移动一帧时访问绑定异常err
:
$ python test.py
--Return--
> test.py(5)_tmp()->None
-> breakpoint()
(Pdb) up
> test.py(6)<module>()
-> _tmp()
(Pdb) p err
ValueError('test')
通过dis
反汇编
下面我比较了两个版本,一个直接使用 breakpoint
另一个用自定义函数包装它 _breakpoint
:
def _breakpoint():
breakpoint()
try:
raise ValueError('test')
except ValueError as err:
breakpoint() # version (a), cannot refer to 'err'
# _breakpoint() # version (b), can refer to 'err'
dis
的输出是相似的,除了一些内存位置和当然函数的名称:
所以一定是额外的堆栈帧允许pdb
引用绑定的异常实例。但是不清楚为什么会这样,因为在 except
块中任何东西都可以引用绑定的异常实例。
这是一个很好的问题!
当发生一些奇怪的事情时,我总是会解开assemble Python 代码并查看字节码。
这可以通过标准库中的 dis
模块来完成。
这里有一个问题,当代码中有断点时,我无法取消-assemble :-)
所以,我稍微修改了代码,并设置了一个标记变量 abc = 10
以使 except
语句之后发生的事情可见。
这是我修改后的代码,我保存为main.py
。
try:
raise ValueError('test')
except ValueError as err:
abc = 10
然后当您取消 assemble 代码时...
❯ python -m dis main.py
1 0 SETUP_FINALLY 12 (to 14)
2 2 LOAD_NAME 0 (ValueError)
4 LOAD_CONST 0 ('test')
6 CALL_FUNCTION 1
8 RAISE_VARARGS 1
10 POP_BLOCK
12 JUMP_FORWARD 38 (to 52)
3 >> 14 DUP_TOP
16 LOAD_NAME 0 (ValueError)
18 COMPARE_OP 10 (exception match)
20 POP_JUMP_IF_FALSE 50
22 POP_TOP
24 STORE_NAME 1 (err)
26 POP_TOP
28 SETUP_FINALLY 8 (to 38)
4 30 LOAD_CONST 1 (10)
32 STORE_NAME 2 (abc)
34 POP_BLOCK
36 BEGIN_FINALLY
>> 38 LOAD_CONST 2 (None)
40 STORE_NAME 1 (err)
42 DELETE_NAME 1 (err)
44 END_FINALLY
46 POP_EXCEPT
48 JUMP_FORWARD 2 (to 52)
>> 50 END_FINALLY
>> 52 LOAD_CONST 2 (None)
54 RETURN_VALUE
你能感觉到发生了什么。
您可以在出色的文档或本周的 Python 模块 网站上阅读有关 dis
模块的更多信息:
https://docs.python.org/3/library/dis.html
https://docs.python.org/3/library/dis.html
当然,这不是一个完美的答案。实际上,我必须自己坐下来阅读文档。我很惊讶 SETUP_FINALLY
在处理 except
块中的变量 abc
之前被调用。另外,我不确定 POP_TOP
的效果是什么 - 在存储 err
名称后立即执行。
P.S.: 好问题!结果如何,我非常兴奋。
breakpoint()
is not a breakpoint in the sense that it halts execution at the exact location of this function call. Instead it's a shorthand for import pdb; pdb.set_trace()
which will halt execution at the next line of code (it calls sys.settrace
在幕后)。由于 except
块中没有更多代码,执行将在 块退出后 停止,因此名称 err
已被删除。通过在 except
块之后添加一行代码可以更清楚地看到这一点:
try:
raise ValueError('test')
except ValueError as err:
breakpoint()
print()
给出以下内容:
$ python test.py
> test.py(5)<module>()
-> print()
这意味着解释器即将执行第 5 行中的 print()
语句并且它已经执行了它之前的所有内容(包括删除名称 err
)。
当使用另一个函数包装 breakpoint()
时,解释器将在该函数的 return
事件处停止执行,因此 except
块尚未退出(并且 err
仍然可用):
$ python test.py
--Return--
> test.py(5)<lambda>()->None
-> (lambda: breakpoint())()
except
块的退出也可以通过在 breakpoint()
:
之后添加一个额外的 pass
语句来延迟
try:
raise ValueError('test')
except ValueError as err:
breakpoint()
pass
这导致:
$ python test.py
> test.py(5)<module>()
-> pass
(Pdb) p err
ValueError('test')
注意pass
必须单独放在一行,否则会被跳过:
$ python test.py
--Return--
> test.py(4)<module>()->None
-> breakpoint(); pass
(Pdb) p err
*** NameError: name 'err' is not defined
注意 --Return--
这意味着解释器已经到达模块的末尾。
如何访问异常实例?
好吧,这是简单的部分。使用 breakpoint()
时,我总是只复制 err
变量;
try:
raise ValueError('foo')
except Exception as err:
e = err
breakpoint()
产生
PS C:\tmp> python .\test_exc.py
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 22:20:52) [MSC v.1916 32 bit (Intel)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: e
Out[1]: ValueError('foo')
(我使用 IPython.embed()
而不是 pdb.set_trace()
作为我的 PYTHONBREAKPOINT
)
为什么会这样?
也许看看 try statement documentation 会有帮助。它说:
When an exception has been assigned using as target, it is cleared at
the end of the except clause. This is as if
except E as N:
foo
was translated to
except E as N:
try:
foo
finally:
del N
This means the exception must be assigned to a different name to be
able to refer to it after the except clause. Exceptions are cleared
because with the traceback attached to them, they form a reference
cycle with the stack frame, keeping all locals in that frame alive
until the next garbage collection occurs.
现在,显然,如果 pdb.set_trace()
(或 IPython.embed()
)位于异常块的最后一行,它将退出异常块并执行隐式 finally
套件.
考虑以下示例:
try:
raise ValueError('test')
except ValueError as err:
breakpoint() # at this point in the debugger, name 'err' is not defined
此处,输入breakpoint后,调试器无权访问绑定到err
的异常实例:
$ python test.py
--Return--
> test.py(4)<module>()->None
-> breakpoint()
(Pdb) p err
*** NameError: name 'err' is not defined
为什么会这样?如何访问异常实例?目前我正在使用以下解决方法,但感觉很尴尬:
try:
raise ValueError('test')
except ValueError as err:
def _tmp():
breakpoint()
_tmp()
# (lambda: breakpoint())() # or this one alternatively
有趣的是,使用这个版本,我还可以在调试器中向上移动一帧时访问绑定异常err
:
$ python test.py
--Return--
> test.py(5)_tmp()->None
-> breakpoint()
(Pdb) up
> test.py(6)<module>()
-> _tmp()
(Pdb) p err
ValueError('test')
通过dis
反汇编
下面我比较了两个版本,一个直接使用 breakpoint
另一个用自定义函数包装它 _breakpoint
:
def _breakpoint():
breakpoint()
try:
raise ValueError('test')
except ValueError as err:
breakpoint() # version (a), cannot refer to 'err'
# _breakpoint() # version (b), can refer to 'err'
dis
的输出是相似的,除了一些内存位置和当然函数的名称:
所以一定是额外的堆栈帧允许pdb
引用绑定的异常实例。但是不清楚为什么会这样,因为在 except
块中任何东西都可以引用绑定的异常实例。
这是一个很好的问题!
当发生一些奇怪的事情时,我总是会解开assemble Python 代码并查看字节码。
这可以通过标准库中的 dis
模块来完成。
这里有一个问题,当代码中有断点时,我无法取消-assemble :-)
所以,我稍微修改了代码,并设置了一个标记变量 abc = 10
以使 except
语句之后发生的事情可见。
这是我修改后的代码,我保存为main.py
。
try:
raise ValueError('test')
except ValueError as err:
abc = 10
然后当您取消 assemble 代码时...
❯ python -m dis main.py
1 0 SETUP_FINALLY 12 (to 14)
2 2 LOAD_NAME 0 (ValueError)
4 LOAD_CONST 0 ('test')
6 CALL_FUNCTION 1
8 RAISE_VARARGS 1
10 POP_BLOCK
12 JUMP_FORWARD 38 (to 52)
3 >> 14 DUP_TOP
16 LOAD_NAME 0 (ValueError)
18 COMPARE_OP 10 (exception match)
20 POP_JUMP_IF_FALSE 50
22 POP_TOP
24 STORE_NAME 1 (err)
26 POP_TOP
28 SETUP_FINALLY 8 (to 38)
4 30 LOAD_CONST 1 (10)
32 STORE_NAME 2 (abc)
34 POP_BLOCK
36 BEGIN_FINALLY
>> 38 LOAD_CONST 2 (None)
40 STORE_NAME 1 (err)
42 DELETE_NAME 1 (err)
44 END_FINALLY
46 POP_EXCEPT
48 JUMP_FORWARD 2 (to 52)
>> 50 END_FINALLY
>> 52 LOAD_CONST 2 (None)
54 RETURN_VALUE
你能感觉到发生了什么。
您可以在出色的文档或本周的 Python 模块 网站上阅读有关 dis
模块的更多信息:
https://docs.python.org/3/library/dis.html https://docs.python.org/3/library/dis.html
当然,这不是一个完美的答案。实际上,我必须自己坐下来阅读文档。我很惊讶 SETUP_FINALLY
在处理 except
块中的变量 abc
之前被调用。另外,我不确定 POP_TOP
的效果是什么 - 在存储 err
名称后立即执行。
P.S.: 好问题!结果如何,我非常兴奋。
breakpoint()
is not a breakpoint in the sense that it halts execution at the exact location of this function call. Instead it's a shorthand for import pdb; pdb.set_trace()
which will halt execution at the next line of code (it calls sys.settrace
在幕后)。由于 except
块中没有更多代码,执行将在 块退出后 停止,因此名称 err
已被删除。通过在 except
块之后添加一行代码可以更清楚地看到这一点:
try:
raise ValueError('test')
except ValueError as err:
breakpoint()
print()
给出以下内容:
$ python test.py
> test.py(5)<module>()
-> print()
这意味着解释器即将执行第 5 行中的 print()
语句并且它已经执行了它之前的所有内容(包括删除名称 err
)。
当使用另一个函数包装 breakpoint()
时,解释器将在该函数的 return
事件处停止执行,因此 except
块尚未退出(并且 err
仍然可用):
$ python test.py
--Return--
> test.py(5)<lambda>()->None
-> (lambda: breakpoint())()
except
块的退出也可以通过在 breakpoint()
:
pass
语句来延迟
try:
raise ValueError('test')
except ValueError as err:
breakpoint()
pass
这导致:
$ python test.py
> test.py(5)<module>()
-> pass
(Pdb) p err
ValueError('test')
注意pass
必须单独放在一行,否则会被跳过:
$ python test.py
--Return--
> test.py(4)<module>()->None
-> breakpoint(); pass
(Pdb) p err
*** NameError: name 'err' is not defined
注意 --Return--
这意味着解释器已经到达模块的末尾。
如何访问异常实例?
好吧,这是简单的部分。使用 breakpoint()
时,我总是只复制 err
变量;
try:
raise ValueError('foo')
except Exception as err:
e = err
breakpoint()
产生
PS C:\tmp> python .\test_exc.py
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 22:20:52) [MSC v.1916 32 bit (Intel)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: e
Out[1]: ValueError('foo')
(我使用 IPython.embed()
而不是 pdb.set_trace()
作为我的 PYTHONBREAKPOINT
)
为什么会这样?
也许看看 try statement documentation 会有帮助。它说:
When an exception has been assigned using as target, it is cleared at the end of the except clause. This is as if
except E as N: foo
was translated to
except E as N: try: foo finally: del N
This means the exception must be assigned to a different name to be able to refer to it after the except clause. Exceptions are cleared because with the traceback attached to them, they form a reference cycle with the stack frame, keeping all locals in that frame alive until the next garbage collection occurs.
现在,显然,如果 pdb.set_trace()
(或 IPython.embed()
)位于异常块的最后一行,它将退出异常块并执行隐式 finally
套件.