Question: Consider the following example: try: raise ValueError('test') except ValueError as err: breakpoint() # at this point in the debugger, name 'err' is not defined  Here, after the breakpoint is entered, the debugger doesn't have access to the exception instance bound to err: $ python test.py --Return-- > test.py(4)<module>()->None -> breakpoint() (Pdb) p err *** NameError: name 'err' is not defined  Why is this the case? How can I access the exception instance? Currently I'm using the following workaround but it feels awkward: try: raise ValueError('test') except ValueError as err: def _tmp(): breakpoint() _tmp() # (lambda: breakpoint())() # or this one alternatively  Interestingly, using this version, I can also access the bound exception err when moving one frame up in the debugger: $ python test.py --Return-- > test.py(5)_tmp()->None -> breakpoint() (Pdb) up > test.py(6)<module>() -> _tmp() (Pdb) p err ValueError('test')  Disassembly via dis In the following I compared two versions, one using breakpoint directly and the other wrapping it in a custom function _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'  The output of dis is similar except for some memory locations and the name of the function of course:  So it must be the additional stack frame that allows pdb to refer to the bound exception instance. However it is not clear why this is the case, since within the except block anything can refer to the bound exception instance.

Answer: 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 under the covers). Since there is no more code inside the except block, execution will halt after that block has been exited and hence the name err is already deleted. This can be seen more clearly by putting an additional line of code after the except block: try: raise ValueError('test') except ValueError as err: breakpoint() print()  which gives the following: $ python test.py > test.py(5)<module>() -> print()  This means the interpreter is about to execute the print() statement in line 5 and it has already executed everything prior to it (including deletion of the name err). When using another function to wrap the breakpoint() then the interpreter will halt execution at the return event of that function and hence the except block is not yet exited (and err is still available): $ python test.py --Return-- > test.py(5)<lambda>()->None -> (lambda: breakpoint())()  Exiting of the except block can also be delayed by putting an additional pass statement after the breakpoint(): try: raise ValueError('test') except ValueError as err: breakpoint() pass  which results in: $ python test.py > test.py(5)<module>() -> pass (Pdb) p err ValueError('test')  Note that the pass has to be put on a separate line, otherwise it will be skipped: $ python test.py --Return-- > test.py(4)<module>()->None -> breakpoint(); pass (Pdb) p err *** NameError: name 'err' is not defined  Note the --Return-- which means the interpreter has already reached the end of the module.