I have the following function defined in my Spyder startup file:
# startup.py
#-----------
def del_globals(*names):
for name1 in names:
try: del globals()[name1]
except KeyError: pass # Ignore if name1 doesn't exist
At the Spyder console, I run the following script with the debugger to create a variable cat
before descending into a function:
# MyScript.py
#------------
runfile('The/Path/to/startup.py') # In case functions are deleted
cat='dog'
del_globals('cat')
I have a breakpoint at the last line del_globals('cat')
. Upon breaking, I confirm that cat
is a variable in the global namespace:
sorted(globals().keys())
# ['TKraiseCFG', '__builtins__', '__doc__', '__file__',
# '__loader__', '__name__', '__nonzero__', '__package__',
# '__spec__', 'cat', 'del_globals']
I then step into del_globals('cat')
and at the very first line (the for
statement), confirmed that cat
is not in the global namespace
sorted(globals().keys())
['TKraiseCFG', '__builtins__', '__doc__', '__loader__',
'__name__', '__nonzero__', '__package__', '__spec__',
'_spyderpdb_builtins_locals', '_spyderpdb_code',
'_spyderpdb_locals', 'del_globals']
Should there be just one global namespace, and should that be the prevailing namespace at the REPL command line?
P.S. For background, the context is described here and here.
globals in Python are really per module.
So, the call to globals()
inside startup.py
file will return you the global variables defined in that file (which will include the del_globals
function itself.
What Spyder is doing is injecting all function definitions from startup.py
in the global namespace of the module used interactiely at the console. This injection is equivalent to what one gets in statements like from startup import del_globals
: a reference to the function in the other module is bound to the current module globals with the same name. But the function object itself is bound to the global variables from that module - it is easy to introspect this, they are bound to the __globals__
attribute of the function itself:
import startup
from startup import del_globals
del_globals.__globals__ is startup.__dict__
# should return True
The fix for this is possible, but requires advancing on Python introspection capabilities: a function can "know" the running environment from where it was called by introspecting Frame
objects - these are runtime objects that bind together various stateful attributes needed to run any python code - and form a stack, one frame pointing to the one that preceded it in its .f_back
attribute - and they also contain a reference to their own "globals" namespace.
All that said, this should work as you intend:
import inspect
def del_globals(*names):
caller_frame = inspect.currentframe().f_back
caller_globals = caller_frame.f_globals
for name1 in names:
try: del caller_globals[name1]
except KeyError: pass # Ignore if name1 doesn't exist