From 82adfa53a0fa5d1c99e63a087b9b4cfdc5f13abc 2007-09-14 06:35:44
From: fperez
Date: 2007-09-14 06:35:44
Subject: [PATCH] - More fixes for doctest support.
- Fix strange namespace problems reported by Darren.

---

diff --git a/IPython/Magic.py b/IPython/Magic.py
index abee16f..bab2bdd 100644
--- a/IPython/Magic.py
+++ b/IPython/Magic.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 """Magic functions for InteractiveShell.
 
-$Id: Magic.py 2754 2007-09-09 10:16:59Z fperez $"""
+$Id: Magic.py 2763 2007-09-14 06:35:44Z fperez $"""
 
 #*****************************************************************************
 #       Copyright (C) 2001 Janko Hauser <jhauser@zscout.de> and
@@ -1037,6 +1037,10 @@ Currently the magic system has the following functions:\n"""
         user_ns = self.shell.user_ns
         for i in self.magic_who_ls():
             del(user_ns[i])
+            
+        # Also flush the private list of module references kept for script
+        # execution protection
+        self.shell._user_main_modules[:] = []
 
     def magic_logstart(self,parameter_s=''):
         """Start logging anywhere in a session.
@@ -1519,11 +1523,13 @@ Currently the magic system has the following functions:\n"""
         sys.argv = [filename]+ arg_lst[1:]  # put in the proper filename
 
         if opts.has_key('i'):
+            # Run in user's interactive namespace
             prog_ns = self.shell.user_ns
             __name__save = self.shell.user_ns['__name__']
             prog_ns['__name__'] = '__main__'
             main_mod = FakeModule(prog_ns)
         else:
+            # Run in a fresh, empty namespace
             if opts.has_key('n'):
                 name = os.path.splitext(os.path.basename(filename))[0]
             else:
@@ -1531,6 +1537,10 @@ Currently the magic system has the following functions:\n"""
             main_mod = FakeModule()
             prog_ns = main_mod.__dict__
             prog_ns['__name__'] = name
+            # The shell MUST hold a reference to main_mod so after %run exits,
+            # the python deletion mechanism doesn't zero it out (leaving
+            # dangling references)
+            self.shell._user_main_modules.append(main_mod)
 
         # Since '%run foo' emulates 'python foo.py' at the cmd line, we must
         # set the __file__ global in the script's namespace
@@ -1542,7 +1552,7 @@ Currently the magic system has the following functions:\n"""
             restore_main = sys.modules['__main__']
         else:
             restore_main = False
-            
+
         sys.modules[prog_ns['__name__']] = main_mod
         
         stats = None
@@ -1594,6 +1604,7 @@ Currently the magic system has the following functions:\n"""
                     if runner is None:
                         runner = self.shell.safe_execfile
                     if opts.has_key('t'):
+                        # timed execution
                         try:
                             nruns = int(opts['N'][0])
                             if nruns < 1:
@@ -1627,6 +1638,7 @@ Currently the magic system has the following functions:\n"""
                             print "  System: %10s s, %10s s." % (t_sys,t_sys/nruns)
                             
                     else:
+                        # regular execution
                         runner(filename,prog_ns,prog_ns,exit_ignore=exit_ignore)
                 if opts.has_key('i'):
                     self.shell.user_ns['__name__'] = __name__save
diff --git a/IPython/genutils.py b/IPython/genutils.py
index 23a7f2a..8ac4ba3 100644
--- a/IPython/genutils.py
+++ b/IPython/genutils.py
@@ -5,7 +5,7 @@ General purpose utilities.
 This is a grab-bag of stuff I find useful in most programs I write. Some of
 these things are also convenient when working at the command line.
 
-$Id: genutils.py 2727 2007-09-07 15:32:08Z vivainio $"""
+$Id: genutils.py 2763 2007-09-14 06:35:44Z fperez $"""
 
 #*****************************************************************************
 #       Copyright (C) 2001-2006 Fernando Perez. <fperez@colorado.edu>
@@ -22,6 +22,7 @@ __license__ = Release.license
 # required modules from the Python standard library
 import __main__
 import commands
+import doctest
 import os
 import re
 import shlex
@@ -836,6 +837,36 @@ def dhook_wrap(func,*a,**k):
     return f
 
 #----------------------------------------------------------------------------
+def doctest_reload():
+    """Properly reload doctest to reuse it interactively.
+
+    This routine:
+
+      - reloads doctest
+      
+      - resets its global 'master' attribute to None, so that multiple uses of
+      the module interactively don't produce cumulative reports.
+    
+      - Monkeypatches its core test runner method to protect it from IPython's
+      modified displayhook.  Doctest expects the default displayhook behavior
+      deep down, so our modification breaks it completely.  For this reason, a
+      hard monkeypatch seems like a reasonable solution rather than asking
+      users to manually use a different doctest runner when under IPython."""
+
+    import doctest
+    reload(doctest)
+    doctest.master=None
+
+    try:
+        doctest.DocTestRunner
+    except AttributeError:
+        # This is only for python 2.3 compatibility, remove once we move to
+        # 2.4 only.
+        pass
+    else:
+        doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
+
+#----------------------------------------------------------------------------
 class HomeDirError(Error):
     pass
 
diff --git a/IPython/iplib.py b/IPython/iplib.py
index f10ccd1..743705c 100644
--- a/IPython/iplib.py
+++ b/IPython/iplib.py
@@ -6,7 +6,7 @@ Requires Python 2.3 or newer.
 
 This file contains all the classes and helper functions specific to IPython.
 
-$Id: iplib.py 2754 2007-09-09 10:16:59Z fperez $
+$Id: iplib.py 2763 2007-09-14 06:35:44Z fperez $
 """
 
 #*****************************************************************************
@@ -41,7 +41,6 @@ import StringIO
 import bdb
 import cPickle as pickle
 import codeop
-import doctest
 import exceptions
 import glob
 import inspect
@@ -344,6 +343,20 @@ class InteractiveShell(object,Magic):
                 #print 'main_name:',main_name # dbg
                 sys.modules[main_name] = FakeModule(self.user_ns)
 
+        # Now that FakeModule produces a real module, we've run into a nasty
+        # problem: after script execution (via %run), the module where the user
+        # code ran is deleted.  Now that this object is a true module (needed
+        # so docetst and other tools work correctly), the Python module
+        # teardown mechanism runs over it, and sets to None every variable
+        # present in that module.  This means that later calls to functions
+        # defined in the script (which have become interactively visible after
+        # script exit) fail, because they hold references to objects that have
+        # become overwritten into None.  The only solution I see right now is
+        # to protect every FakeModule used by %run by holding an internal
+        # reference to it.  This private list will be used for that.  The
+        # %reset command will flush it as well.
+        self._user_main_modules = []
+
         # List of input with multi-line handling.
         # Fill its zero entry, user counter starts at 1
         self.input_hist = InputList(['\n'])
@@ -681,21 +694,10 @@ class InteractiveShell(object,Magic):
         self.sys_displayhook = sys.displayhook
         sys.displayhook = self.outputcache
 
-        # Monkeypatch doctest so that its core test runner method is protected
-        # from IPython's modified displayhook.  Doctest expects the default
-        # displayhook behavior deep down, so our modification breaks it
-        # completely.  For this reason, a hard monkeypatch seems like a
-        # reasonable solution rather than asking users to manually use a
-        # different doctest runner when under IPython.
-        try:
-            doctest.DocTestRunner
-        except AttributeError:
-            # This is only for python 2.3 compatibility, remove once we move to
-            # 2.4 only.
-            pass
-        else:
-            doctest.DocTestRunner.run = dhook_wrap(doctest.DocTestRunner.run)
-
+        # Do a proper resetting of doctest, including the necessary displayhook
+        # monkeypatching
+        doctest_reload()
+        
         # Set user colors (don't do it in the constructor above so that it
         # doesn't crash if colors option is invalid)
         self.magic_colors(rc.colors)
diff --git a/doc/ChangeLog b/doc/ChangeLog
index 9d942da..184917f 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -1,3 +1,15 @@
+2007-09-14  Fernando Perez  <Fernando.Perez@colorado.edu>
+
+	* IPython/genutils.py (doctest_reload): expose the doctest
+	reloader to the user so that people can easily reset doctest while
+	using it interactively.  Fixes a problem reported by Jorgen.
+
+	* IPython/iplib.py (InteractiveShell.__init__): protect the
+	FakeModule instances used for __main__ in %run calls from
+	deletion, so that user code defined in them isn't left with
+	dangling references due to the Python module deletion machinery.
+	This should fix the problems reported by Darren.
+
 2007-09-10  Darren Dale  <dd55@cornell.edu>
 
         * Cleanup of IPShellQt and IPShellQt4