Accessing globals after wrong code.interact() call
Have you ever called code.interact()
and forgot to pass local=locals()
or
local={**globals(), **locals()}
. Most of the time you may just exit interactive console, add
missing parameters and run program again. But what if the program was executing for couple of hours
before interactive console was started? You might want to access e.g. global variables without
running it again. Fortunately Python is a language for adults, so it’s totally doable.
The first option to access all variables etc is to use sys._getframe
:
>>> import sys
>>> frame = sys._getframe(6) # in my setup it happens to be 6th frame
>>> database_index = frame.f_globals['dataframe_index']
But in the help of _getframe
we can read:
This function should be used for internal and specialized purposes only.
Fortunately there’s another module that also does what we want: inspect
. It’s a little bit
more verbose, but is not private.
>>> import inspect
>>> database_index = inspect.stack()[6].frame.f_globals['database_index']
But frankly speaking if you look at inspect
’s code you discover that it uses sys
under the
bonnet. So my suggestion is to:
- use
sys._getframe
in emergency situations (like in interactive console) - use
inspect
module if you are doing this in a script
Obviously instead of code.interact
an alternative can be used: pdb.set_trace
. It doesn’t
suffer such problems at all.
Bonus: python frames and surprising setter of f_lineno
Out of curiosity I looked into CPython sources. It looks like _getframe
function does simple O(n)
stack traversal. It retrieves frames from current thread state.
_PyThreadState_GET
as name suggests returns thread state object, where frame
is one of the most
important fields. Quick look at the definition of frame struct reveals what potentially can be done
with it: f_back
, f_code
, f_globals
, f_locals
, f_lineno
etc. My inner hacker woke up and
I tried to change f_lineno
of a frame to see what happens:
This error is baked right into CPython! Apparently frame_setlineno
function bails out when
the caller is not a trace function. From the docs of the function we can also learn that f_lineno
is used by tracking mechanism. It also describes some exceptions where you cannot jump:
- Lines with an ‘except’ statement on them can’t be jumped to, because they expect an exception to be on the top of the stack.
- Lines that live in a ‘finally’ block can’t be jumped from or to, since the END_FINALLY expects to clean up the stack after the ‘try’ block.
- ‘try’, ‘with’ and ‘async with’ blocks can’t be jumped into because the blockstack needs to be set up before their code runs.
- ‘for’ and ‘async for’ loops can’t be jumped into because the iterator needs to be on the stack.
- Jumps cannot be made from within a trace function invoked with a ‘return’ or ‘exception’ event since the eval loop has been exited at that time.
I can only say that whatever detail you pick it becomes a rabbit hole. This is so beautiful.