Issue 36537: except statement block incorrectly assumes end of scope(?). (original) (raw)

Created on 2019-04-05 18:43 by Saim Raza, last changed 2022-04-11 14:59 by admin.

Messages (12)
msg339510 - (view) Author: Saim Raza (Saim Raza) Date: 2019-04-05 18:43
If pdb.set_trace() is the last statement in the first code snippet, variable 'err' is (wrongly?) excluded from locals(). Adding any code after the pdb.set_trace() statement makes 'err' available in locals. In [2]: try: ...: raise ValueError("I am ValueError") ...: except ValueError as err: ...: print("err" in locals()) ...: import pdb; pdb.set_trace() ...: True --Return-- > (5)()->None -> import pdb; pdb.set_trace() (Pdb) print("err" in locals()) False <-------------- BUG?? (Pdb) c In [3]: try: ...: raise ValueError("I am ValueError") ...: except ValueError as err: ...: print("err" in locals()) ...: import pdb; pdb.set_trace() ...: import os # Dummy code - makes variable 'err' available inside the debugger. ...: True > (6)()->None -> import os # Dummy code - makes variable err available inside debugger (Pdb) print("err" in locals()) True In [4]: sys.version_info Out[4]: sys.version_info(major=3, minor=7, micro=3, releaselevel='final', serial=0) FTR, the variable 'err' is available in both cases in case of Python 2.7. Also, this happens with ipdb as well. Please note that I am aware that I need to assign the variable 'err' to some variable inside the except block to access it outside the except block. However, the pdb statement is still inside the except block in both cases.
msg339517 - (view) Author: SilentGhost (SilentGhost) * (Python triager) Date: 2019-04-05 19:25
Can confirm also on 3.6, but this seems to be something particular to set_trace, when pdb is entered via post_mortem, the variable is available in locals().
msg339542 - (view) Author: Xavier de Gaye (xdegaye) * (Python triager) Date: 2019-04-06 19:48
This is not a bug. On the first pdb session from the OP: True --Return-- > (5)()->None -> import pdb; pdb.set_trace() (Pdb) print("err" in locals()) False <-------------- BUG?? Pdb has stopped on a '--Return--' line event, so we are already outside the scope of the except clause and the 'err' local variable cannot be accessed outside the except block. The following code snippet shows that pdb stops at the line following the set_trace() call: ================== def foo(): try: 1/0 except Exception as err: import pdb; pdb.set_trace() x= 1 foo() ================== And the corresponding pdb session: $ python foo.py > /path/to/foo.py(6)foo() -> x= 1 (Pdb) err *** NameError: name 'err' is not defined (Pdb)
msg339550 - (view) Author: Saim Raza (Saim Raza) Date: 2019-04-06 21:52
https://docs.python.org/3/reference/compound_stmts.html#the-try-statement says ============= except E as N: foo ============= is converted to the following statement: ============= except E as N: try: foo finally: del N ============= In the examples in this thread, foo is 'import pdb; pdb.set_trace()'. So, I was expecting 'del N' to be executed only after executing foo. This implies that err should have been available in the scope. Further, isn't the code execution supposed to be stopped at the set_trace() line and not on the next line. Should it at least respect the scope (indentation) and not execute the '--Return--' line event until the user actually executes the next line (x=1 in your example)?
msg339566 - (view) Author: Xavier de Gaye (xdegaye) * (Python triager) Date: 2019-04-07 09:29
@Saim wrote > https://docs.python.org/3/reference/compound_stmts.html#the-try-statement says This is correct, the disassembly of foo.py below demonstrates that point: BEGIN_FINALLY at bytecode index 36 starts the implementation of the 'finally' clause that deletes 'err'. But pdb is a line debugger, not a debugger at the bytecode level, so when 'foo = 1' is replaced by 'import pdb; pdb.set_trace()', the first line where pdb may stop is at line 6 and since foo() last line is line 5, pdb stops at the 'return' event for this function. ======= foo.py ======== def foo(): try: 1/0 except Exception as err: foo = 1 import dis dis.dis(foo) =========================== 2 0 SETUP_FINALLY 12 (to 14) 3 2 LOAD_CONST 1 (1) 4 LOAD_CONST 2 (0) 6 BINARY_TRUE_DIVIDE 8 POP_TOP 10 POP_BLOCK 12 JUMP_FORWARD 38 (to 52) 4 >> 14 DUP_TOP 16 LOAD_GLOBAL 0 (Exception) 18 COMPARE_OP 10 (exception match) 20 POP_JUMP_IF_FALSE 50 22 POP_TOP 24 STORE_FAST 0 (err) 26 POP_TOP 28 SETUP_FINALLY 8 (to 38) 5 30 LOAD_CONST 1 (1) 32 STORE_FAST 1 (foo) 34 POP_BLOCK 36 BEGIN_FINALLY >> 38 LOAD_CONST 0 (None) 40 STORE_FAST 0 (err) 42 DELETE_FAST 0 (err) 44 END_FINALLY 46 POP_EXCEPT 48 JUMP_FORWARD 2 (to 52) >> 50 END_FINALLY >> 52 LOAD_CONST 0 (None) 54 RETURN_VALUE ===========================
msg339577 - (view) Author: Xavier de Gaye (xdegaye) * (Python triager) Date: 2019-04-07 15:06
The *** Tracing *** section [1] of Objects/lnotab_notes.txt in the cpython repository provides detailed explanations for when the line trace function is called. [1] https://github.com/python/cpython/blob/9d7b2c0909b78800d1376fd696f73824ea680463/Objects/lnotab_notes.txt#L63
msg340033 - (view) Author: Saim Raza (Saim Raza) Date: 2019-04-12 12:37
This is pretty unintuitive from a user's stand point. Now, I need to *inconveniently* put some dummy code after the ser_trace call every time I want to access the exception inside the except block. Also, this is a change in behavior from Python 2.7. Is this documented somewhere?
msg340035 - (view) Author: SilentGhost (SilentGhost) * (Python triager) Date: 2019-04-12 12:39
Saim, a .post_mortem could be used instead. As I noted, it works just fine.
msg340036 - (view) Author: Saim Raza (Saim Raza) Date: 2019-04-12 13:04
Thanks, SilentGhost! However, should we try to fix set_trace as well to avoid hassles to other users?
msg340043 - (view) Author: SilentGhost (SilentGhost) * (Python triager) Date: 2019-04-12 13:41
I cannot imagine that the fix would be straightforward or that there is much use of this particular pattern. Perhaps, a note in the docs suggesting post_mortem() for except clauses over set_trace() would be more appropriate.
msg340114 - (view) Author: Xavier de Gaye (xdegaye) * (Python triager) Date: 2019-04-12 21:32
> Now, I need to *inconveniently* put some dummy code after the ser_trace call every time I want to access the exception inside the except block Knowing that pdb stops just before the interpreter executes the line after pdb.set_trace(), what prevents you from writing the pdb.set_trace() statement before the last line of the except clause ?
msg340117 - (view) Author: Saim Raza (Saim Raza) Date: 2019-04-12 22:24
> Knowing that pdb stops just before the interpreter executes the line after pdb.set_trace(), what prevents you from writing the pdb.set_trace() statement before the last line of the except clause ? Of course, that can be done. The issue here is to get the correct and expected behavior from pdb/ipdb. On a similar note, users might want to put just the set_trace() call and no other code in the block while writing the code or during debugging the exception caught.
History
Date User Action Args
2022-04-11 14:59:13 admin set github: 80718
2019-04-12 22:24:17 Saim Raza set messages: +
2019-04-12 21:32:55 xdegaye set messages: +
2019-04-12 13:41:10 SilentGhost set messages: +
2019-04-12 13:04:23 Saim Raza set messages: +
2019-04-12 12:39:11 SilentGhost set messages: +
2019-04-12 12:37:22 Saim Raza set messages: +
2019-04-07 15:06:57 xdegaye set messages: +
2019-04-07 09:29:42 xdegaye set messages: +
2019-04-06 21:52:06 Saim Raza set messages: +
2019-04-06 19:48:02 xdegaye set messages: +
2019-04-06 17:45:21 xtreak set nosy: + xdegaye
2019-04-05 19:25:03 SilentGhost set nosy: + barry, SilentGhostmessages: +
2019-04-05 18:43:36 Saim Raza create