From 279211b62dc7a4b01fc526cfe81a41a322ec6b16 2013-07-05 23:38:54 From: Thomas Kluyver Date: 2013-07-05 23:38:54 Subject: [PATCH] Simplify caching of modules from %run-ing scripts. Previously, we cleared and re-used a single FakeModule instance in which to run cells, and cached copies of the modules' namespaces to prevent them from being cleared. Now, we cache one FakeModule instance per script file, clearing it and re-using it if the same script is re-run. This seems more robust. Closes gh-3547 --- diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 8cbacdb..87f7b28 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -819,16 +819,9 @@ class InteractiveShell(SingletonConfigurable): # Things related to the "main" module #------------------------------------------------------------------------- - def new_main_mod(self,ns=None): + def new_main_mod(self,filename): """Return a new 'main' module object for user code execution. - """ - main_mod = self._user_main_module - init_fakemod_dict(main_mod,ns) - return main_mod - - def cache_main_mod(self,ns,fname): - """Cache a main module's namespace. - + When scripts are executed via %run, we must keep a reference to the namespace of their __main__ module (a FakeModule instance) around so that Python doesn't clear it, rendering objects defined therein @@ -840,32 +833,16 @@ class InteractiveShell(SingletonConfigurable): keep one copy of the namespace (the last one), thus preventing memory leaks from old references while allowing the objects from the last execution to be accessible. - - Note: we can not allow the actual FakeModule instances to be deleted, - because of how Python tears down modules (it hard-sets all their - references to None without regard for reference counts). This method - must therefore make a *copy* of the given namespace, to allow the - original module's __dict__ to be cleared and reused. - - - Parameters - ---------- - ns : a namespace (a dict, typically) - - fname : str - Filename associated with the namespace. - - Examples - -------- - - In [10]: import IPython - - In [11]: _ip.cache_main_mod(IPython.__dict__,IPython.__file__) - - In [12]: IPython.__file__ in _ip._main_ns_cache - Out[12]: True """ - self._main_ns_cache[os.path.abspath(fname)] = ns.copy() + filename = os.path.abspath(filename) + try: + main_mod = self._main_mod_cache[filename] + except KeyError: + main_mod = self._main_mod_cache[filename] = FakeModule() + else: + init_fakemod_dict(main_mod) + + return main_mod def clear_main_mod_cache(self): """Clear the cache of main modules. @@ -877,17 +854,17 @@ class InteractiveShell(SingletonConfigurable): In [15]: import IPython - In [16]: _ip.cache_main_mod(IPython.__dict__,IPython.__file__) + In [16]: m = _ip.new_main_mod(IPython.__file__) - In [17]: len(_ip._main_ns_cache) > 0 + In [17]: len(_ip._main_mod_cache) > 0 Out[17]: True In [18]: _ip.clear_main_mod_cache() - In [19]: len(_ip._main_ns_cache) == 0 + In [19]: len(_ip._main_mod_cache) == 0 Out[19]: True """ - self._main_ns_cache.clear() + self._main_mod_cache.clear() #------------------------------------------------------------------------- # Things related to debugging @@ -1017,10 +994,7 @@ class InteractiveShell(SingletonConfigurable): # and clear_main_mod_cache() methods for details on use. # This is the cache used for 'main' namespaces - self._main_ns_cache = {} - # And this is the single instance of FakeModule whose __dict__ we keep - # copying and clearing for reuse on each %run - self._user_main_module = FakeModule() + self._main_mod_cache = {} # A table holding all the namespaces IPython deals with, so that # introspection facilities can search easily. @@ -1174,8 +1148,8 @@ class InteractiveShell(SingletonConfigurable): Note that this does not include the displayhook, which also caches objects from the output.""" - return [self.user_ns, self.user_global_ns, - self._user_main_module.__dict__] + self._main_ns_cache.values() + return [self.user_ns, self.user_global_ns] + \ + [m.__dict__ for m in self._main_mod_cache.values()] def reset(self, new_session=True): """Clear all internal namespaces, and attempt to release references to @@ -1219,9 +1193,6 @@ class InteractiveShell(SingletonConfigurable): # execution protection self.clear_main_mod_cache() - # Clear out the namespace from the last %run - self.new_main_mod() - def del_var(self, varname, by_name=False): """Delete a variable from the various namespaces, so that, as far as possible, we're not keeping any hidden references to it. diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py index 14470ce..b7662a9 100644 --- a/IPython/core/magics/execution.py +++ b/IPython/core/magics/execution.py @@ -508,7 +508,7 @@ python-profiler package from non-free.""") prog_ns = self.shell.user_ns __name__save = self.shell.user_ns['__name__'] prog_ns['__name__'] = '__main__' - main_mod = self.shell.new_main_mod(prog_ns) + main_mod = self.shell.user_module else: # Run in a fresh, empty namespace if 'n' in opts: @@ -516,7 +516,10 @@ python-profiler package from non-free.""") else: name = '__main__' - main_mod = self.shell.new_main_mod() + # The shell MUST hold a reference to prog_ns so after %run + # exits, the python deletion mechanism doesn't zero it out + # (leaving dangling references). See interactiveshell for details + main_mod = self.shell.new_main_mod(filename) prog_ns = main_mod.__dict__ prog_ns['__name__'] = name @@ -593,10 +596,6 @@ python-profiler package from non-free.""") if 'i' in opts: self.shell.user_ns['__name__'] = __name__save else: - # The shell MUST hold a reference to prog_ns so after %run - # exits, the python deletion mechanism doesn't zero it out - # (leaving dangling references). - self.shell.cache_main_mod(prog_ns, filename) # update IPython interactive namespace # Some forms of read errors on the file may mean the