Permalink
Browse files

Use user_ns as global namespace if it is passed without user_module, …

…and add test for pickling interactively defined objects.

Closes gh-29
  • Loading branch information...
1 parent cccd3b7 commit b4609459482154bf50410a75d6a31f7cd7165fbe @takluyver committed Jul 30, 2011
@@ -888,19 +888,13 @@ def init_create_namespaces(self, user_module=None, user_ns=None):
# 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.
- self.user_module = self.prepare_user_module(user_module)
+ # These routines return a properly built module and dict as needed by
+ # the rest of the code, and can also be used by extension writers to
+ # generate properly initialized namespaces.
+ self.user_module, self.user_ns = self.prepare_user_module(user_module, user_ns)
- if user_ns is None:
- user_ns = self.user_module.__dict__
- self.user_ns = user_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
- # actual interactive use. Since it is always a subset of user_ns, it
- # doesn't need to be separately tracked in the ns_table.
+ # A record of hidden variables we have added to the user namespace, so
+ # we can list later only variables defined in actual interactive use.
self.user_ns_hidden = set()
# Now that FakeModule produces a real module, we've run into a nasty
@@ -943,20 +937,38 @@ def init_create_namespaces(self, user_module=None, user_ns=None):
def user_global_ns(self):
return self.user_module.__dict__
- def prepare_user_module(self, user_module=None):
- """Prepares a module for use as the interactive __main__ module in
- which user code is run.
+ def prepare_user_module(self, user_module=None, user_ns=None):
+ """Prepare the module and namespace in which user code will be run.
+
+ When IPython is started normally, both parameters are None: a new module
+ is created automatically, and its __dict__ used as the namespace.
+
+ If only user_module is provided, its __dict__ is used as the namespace.
+ If only user_ns is provided, a dummy module is created, and user_ns
+ becomes the global namespace. If both are provided (as they may be
+ when embedding), user_ns is the local namespace, and user_module
+ provides the global namespace.
Parameters
----------
user_module : module, optional
The current user module in which IPython is being run. If None,
a clean module will be created.
+ user_ns : dict, optional
+ A namespace in which to run interactive commands.
Returns
-------
- A module object.
+ A tuple of user_module and user_ns, each properly initialised.
"""
+ if user_module is None and user_ns is not None:
+ user_ns.setdefault("__name__", "__main__")
+ class DummyMod(object):
+ "A dummy module used for IPython's interactive namespace."
+ pass
+ user_module = DummyMod()
+ user_module.__dict__ = user_ns
+
if user_module is None:
user_module = types.ModuleType("__main__",
doc="Automatically created module for IPython interactive environment")
@@ -966,8 +978,11 @@ def prepare_user_module(self, user_module=None):
# http://mail.python.org/pipermail/python-dev/2001-April/014068.html
user_module.__dict__.setdefault('__builtin__',__builtin__)
user_module.__dict__.setdefault('__builtins__',__builtin__)
+
+ if user_ns is None:
+ user_ns = user_module.__dict__
- return user_module
+ return user_module, user_ns
def init_sys_modules(self):
# We need to insert into sys.modules something that looks like a
@@ -1075,13 +1090,16 @@ def reset(self, new_session=True):
# The main execution namespaces must be cleared very carefully,
# skipping the deletion of the builtin-related keys, because doing so
# 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('__builtin__')
- drop_keys.discard('__builtins__')
- for k in drop_keys:
- del ns[k]
-
+ if self.user_ns is not self.user_global_ns:
+ self.user_ns.clear()
+ ns = self.user_global_ns
+ drop_keys = set(ns.keys())
+ drop_keys.discard('__builtin__')
+ drop_keys.discard('__builtins__')
+ drop_keys.discard('__name__')
+ for k in drop_keys:
+ del ns[k]
+
# Restore the user namespaces to minimal usability
self.init_user_ns()
@@ -25,6 +25,7 @@
import tempfile
import unittest
from os.path import join
+import sys
from StringIO import StringIO
from IPython.testing import decorators as dec
@@ -128,6 +129,7 @@ def __repr__(self):
f = IPython.core.formatters.PlainTextFormatter()
f([Spam(),Spam()])
+
def test_future_flags(self):
"""Check that future flags are used for parsing code (gh-777)"""
ip = get_ipython()
@@ -151,6 +153,37 @@ def test_future_unicode(self):
finally:
# Reset compiler flags so we don't mess up other tests.
ip.compile.reset_compiler_flags()
+
+ def test_can_pickle(self):
+ "Can we pickle objects defined interactively (GH-29)"
+ ip = get_ipython()
+ ip.reset()
+ 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
+
+ # We need to swap in our main module - this is only necessary
+ # inside the test framework, because IPython puts the interactive module
+ # in place (but the test framework undoes this).
+ _main = sys.modules['__main__']
+ sys.modules['__main__'] = ip.user_module
+ try:
+ res = dumps(ip.user_ns["w"])
+ finally:
+ sys.modules['__main__'] = _main
+ self.assertTrue(isinstance(res, bytes))
+
+ def test_global_ns(self):
+ "Code in functions must be able to access variables outside them."
+ 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)
def test_bad_custom_tb(self):
"""Check that InteractiveShell is protected from bad custom exception handlers"""
@@ -205,8 +205,7 @@ def start_ipython():
# can capture subcommands and print them to Python's stdout, otherwise the
# doctest machinery would miss them.
shell.system = py3compat.MethodType(xsys, shell)
-
-
+
shell._showtraceback = py3compat.MethodType(_showtraceback, shell)
# IPython is ready, now clean up some global state...
@@ -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

0 comments on commit b460945

Please sign in to comment.