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 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. @@ -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 + + * 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 * Cleanup of IPShellQt and IPShellQt4