ipython: IPythonShellEmbed fails to recognize local variables
Issue
Embedded IPython shells can lose track of local variables.
Test Case
Minimal test case:
class Foo(object):
""" Container-like object """
def __setattr__(self, obj, val):
self.__dict__[obj] = val
def __getattr__(self, obj, val):
return self.__dict__[obj]
f = Foo()
f.indices = set([1,2,3,4,5])
f.values = {}
for x in f.indices:
f.values[x] = x
def bar(foo):
import IPython
IPython.Shell.IPShellEmbed()()
return sum(foo.values[x] for x in foo.indices)
print bar(f)
To see the error, first run the code in Python (or IPython) and exit from the spawned shell; the final print statement correctly displays ‘15’. Run the code again, but this time type sum(foo.values[x] for x in foo.indices)
in the spawned shell, and we receive the error
" NameError: global name ‘foo’ is not defined".
About this issue
- Original URL
- State: open
- Created 14 years ago
- Reactions: 3
- Comments: 19 (11 by maintainers)
I looked into this issue a bit, and it’s definitely fixable (though maintaining fixes for both Python 2 and 3 may be messy).
The ChainMap solution would be easiest to include into IPython proper. However, there’s a slight catch that eval/exec require globals to be a
dict
. Creating aclass MyChainMap(ChainMap, dict): pass
can work around this.I also wrote a Python 3.5+ fix based on a different strategy of simulating closure cells and forcing the python compiler to emit the correct bytecode to work with them. The relevant file is here, part of my xdbg demo. It works by replacing
get_ipython().run_ast_nodes
.As far as I can tell, the two approaches differ only in their handling of closures. When
xdbg
is embedded at a scope that has closed over some variables, it can correctly access those variables by reference and mutate them. Additionally, if any functions are created in the interactive interpreter, they will close over any local variables they need while allowing the rest of the local scope to be garbage collected.As a work around, you can use
globals().update(locals())
to carry embedding sessions’ local into process globals.Source: https://stackoverflow.com/a/67517617/695964
Based on https://bugs.python.org/issue13557 and the fact that explicitly passing locals() and globals() to an exec fixes @takluyver’s reproduction of the bug, it seems like this ought to be possible to fix in IPython by passing the correct local and global namespaces.