Skip to content

Allow pickling objects defined interactively. #384

Closed
wants to merge 6 commits into from
View
125 IPython/core/interactiveshell.py
@@ -813,17 +813,44 @@ def init_create_namespaces(self, user_ns=None, user_global_ns=None):
# that if you need to access the built-in namespace directly, you
# should start with "import __builtin__" (note, no 's') which will
# definitely give you a module. Yeah, it's somewhat confusing:-(.
-
- # These routines return properly built dicts as needed by the rest of
- # the code, and can also be used by extension writers to generate
- # properly initialized namespaces.
- user_ns, user_global_ns = self.make_user_namespaces(user_ns,
- user_global_ns)
+
+ # We must ensure that __builtin__ (without the final 's') is always
+ # available and pointing to the __builtin__ *module*. For more details:
+ # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
+
+ separate_user_ns = True
+ if user_ns is None:
+ user_ns = {}
+ separate_user_ns = False
+
+ # Set __name__ to __main__ to better match the behavior of the
+ # normal interpreter.
+ user_ns.setdefault('__name__','__main__')
+ user_ns.setdefault('__builtin__',__builtin__)
+ user_ns.setdefault('__builtins__',__builtin__)
+
+ if (user_global_ns is not None) and \
+ not isinstance(user_global_ns, dict):
+ raise TypeError("user_global_ns must be a true dict; got %r"
+ % type(user_global_ns))
# Assign namespaces
- # This is the namespace where all normal user variables live
- self.user_ns = user_ns
- self.user_global_ns = user_global_ns
+ # user_ns the namespace where all normal user variables live
+ self.user_ns_mod = FakeModule(user_ns)
+ if not separate_user_ns:
+ # In the normal case, we replace user_ns with a module dict, so that
+ # user-defined objects are in the __main__ module, and can be pickled.
+ self.user_ns = self.user_ns_mod.__dict__
+ else:
+ # Otherwise, we will need to manually update it after execution.
+ self.user_ns = user_ns
+
+ # The global namespace will normally be identical to the user namespace.
+ # The exceptions are certain embedding cases.
+ if user_global_ns is None:
+ self.user_global_ns = self.user_ns
+ else:
+ self.user_global_ns = user_global_ns
# An auxiliary namespace that checks what parts of the user_ns were
# loaded at startup, so we can list later only variables defined in
@@ -866,8 +893,8 @@ def init_create_namespaces(self, user_ns=None, user_global_ns=None):
# A table holding all the namespaces IPython deals with, so that
# introspection facilities can search easily.
- self.ns_table = {'user':user_ns,
- 'user_global':user_global_ns,
+ self.ns_table = {'user':self.user_ns,
+ 'user_global':self.user_global_ns,
'internal':self.internal_ns,
'builtin':__builtin__.__dict__
}
@@ -880,65 +907,7 @@ def init_create_namespaces(self, user_ns=None, user_global_ns=None):
# clears them manually and carefully.
self.ns_refs_table = [ self.user_ns_hidden,
self.internal_ns, self._main_ns_cache ]
-
- def make_user_namespaces(self, user_ns=None, user_global_ns=None):
- """Return a valid local and global user interactive namespaces.
-
- This builds a dict with the minimal information needed to operate as a
- valid IPython user namespace, which you can pass to the various
- embedding classes in ipython. The default implementation returns the
- same dict for both the locals and the globals to allow functions to
- refer to variables in the namespace. Customized implementations can
- return different dicts. The locals dictionary can actually be anything
- following the basic mapping protocol of a dict, but the globals dict
- must be a true dict, not even a subclass. It is recommended that any
- custom object for the locals namespace synchronize with the globals
- dict somehow.
-
- Raises TypeError if the provided globals namespace is not a true dict.
-
- Parameters
- ----------
- user_ns : dict-like, optional
- The current user namespace. The items in this namespace should
- be included in the output. If None, an appropriate blank
- namespace should be created.
- user_global_ns : dict, optional
- The current user global namespace. The items in this namespace
- should be included in the output. If None, an appropriate
- blank namespace should be created.
-
- Returns
- -------
- A pair of dictionary-like object to be used as the local namespace
- of the interpreter and a dict to be used as the global namespace.
- """
-
-
- # We must ensure that __builtin__ (without the final 's') is always
- # available and pointing to the __builtin__ *module*. For more details:
- # http://mail.python.org/pipermail/python-dev/2001-April/014068.html
-
- if user_ns is None:
- # Set __name__ to __main__ to better match the behavior of the
- # normal interpreter.
- user_ns = {'__name__' :'__main__',
- '__builtin__' : __builtin__,
- '__builtins__' : __builtin__,
- }
- else:
- user_ns.setdefault('__name__','__main__')
- user_ns.setdefault('__builtin__',__builtin__)
- user_ns.setdefault('__builtins__',__builtin__)
-
- if user_global_ns is None:
- user_global_ns = user_ns
- if type(user_global_ns) is not dict:
- raise TypeError("user_global_ns must be a true dict; got %r"
- % type(user_global_ns))
-
- return user_ns, user_global_ns
-
+
def init_sys_modules(self):
# We need to insert into sys.modules something that looks like a
# module but which accesses the IPython namespace, for shelve and
@@ -962,7 +931,7 @@ def init_sys_modules(self):
except KeyError:
raise KeyError('user_ns dictionary MUST have a "__name__" key')
else:
- sys.modules[main_name] = FakeModule(self.user_ns)
+ sys.modules[main_name] = self.user_ns_mod
def init_user_ns(self):
"""Initialize all user-visible namespaces to their minimum defaults.
@@ -1057,13 +1026,19 @@ def reset(self, new_session=True):
# would cause errors in many object's __del__ methods.
for ns in [self.user_ns, self.user_global_ns]:
drop_keys = set(ns.keys())
+ drop_keys.discard('__name__')
drop_keys.discard('__builtin__')
drop_keys.discard('__builtins__')
for k in drop_keys:
del ns[k]
# Restore the user namespaces to minimal usability
self.init_user_ns()
+
+ # In some situations, e.g. testing, our fake main module has a __dict__
+ # separate from the user_ns: if so, we need to clear it manually.
+ if self.user_ns is not self.user_ns_mod.__dict__:
+ init_fakemod_dict(self.user_ns_mod, self.user_ns)
# Restore the default and user aliases
self.alias_manager.clear_aliases()
@@ -2145,7 +2120,13 @@ def run_cell(self, raw_cell, store_history=True):
self.run_ast_nodes(code_ast.body, cell_name,
interactivity="last_expr")
-
+
+ # In the normal case, user_ns is the __dict__ of the
+ # corresponding module. But e.g. in tests, it isn't, so we need
+ # to copy the contents across.
+ if self.user_ns is not self.user_ns_mod.__dict__:
+ init_fakemod_dict(self.user_ns_mod, self.user_ns)
+
# Execute any registered post-execution functions.
for func, status in self._post_execute.iteritems():
if not status:
View
22 IPython/core/tests/test_interactiveshell.py
@@ -91,3 +91,25 @@ def test_magic_names_in_string(self):
ip = get_ipython()
ip.run_cell('a = """\n%exit\n"""')
self.assertEquals(ip.user_ns['a'], '\n%exit\n')
+
+ def test_can_pickle(self):
+ ip = get_ipython()
+ ip.run_cell(("class Mylist(list):\n"
+ " def __init__(self,x=[]):\n"
+ " list.__init__(self,x)"))
+ ip.run_cell("w=Mylist([1,2,3])")
+
+ from cPickle import dumps
+ res = dumps(ip.user_ns["w"])
+ self.assertTrue(isinstance(res, bytes))
+
+ def test_global_ns(self):
+ ip = get_ipython()
+ ip.run_cell("a = 10")
+ ip.run_cell(("def f(x):"
+ " return x + a"))
+ ip.run_cell("b = f(12)")
+ self.assertEqual(ip.user_ns["b"], 22)
+
+
+
View
5 IPython/testing/globalipapp.py
@@ -82,6 +82,9 @@ def __init__(self,*a):
# namespace in doctests that call '_'.
self.protect_underscore = False
+ # We set this so that the tests don't clash with __main__
+ self["__name__"] = "__test_main__"
+
def clear(self):
dict.clear(self)
self.update(self._savedict)
@@ -158,7 +161,7 @@ def start_ipython():
# Create and initialize our test-friendly IPython instance.
shell = TerminalInteractiveShell.instance(config=config,
user_ns=ipnsdict(),
- user_global_ns={}
+ user_global_ns=None
)
# A few more tweaks needed for playing nicely with doctests...
View
4 IPython/testing/plugin/ipdoctest.py
@@ -271,6 +271,8 @@ def setUp(self):
# for IPython examples *only*, we swap the globals with the ipython
# namespace, after updating it with the globals (which doctest
# fills with the necessary info from the module being tested).
+ self.user_ns_orig = {}
+ self.user_ns_orig.update(_ip.user_ns)
_ip.user_ns.update(self._dt_test.globs)
self._dt_test.globs = _ip.user_ns
# IPython must protect the _ key in the namespace (it can't exist)
@@ -286,6 +288,8 @@ def tearDown(self):
# teardown doesn't destroy the ipython namespace
if isinstance(self._dt_test.examples[0],IPExample):
self._dt_test.globs = self._dt_test_globs_ori
+ _ip.user_ns.clear()
+ _ip.user_ns.update(self.user_ns_orig)
# Restore the behavior of the '_' key in the user namespace to
# normal after each doctest, so that unittests behave normally
_ip.user_ns.protect_underscore = False
Something went wrong with that request. Please try again.