diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 037b86b..e32d32c 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1119,7 +1119,49 @@ class InteractiveShell(SingletonConfigurable, Magic): # 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. + + Parameters + ---------- + varname : str + The name of the variable to delete. + by_name : bool + If True, delete variables with the given name in each + namespace. If False (default), find the variable in the user + namespace, and delete references to it. + """ + if varname in ('__builtin__', '__builtins__'): + raise ValueError("Refusing to delete %s" % varname) + ns_refs = self.ns_refs_table + [self.user_ns, + self.user_global_ns, self._user_main_module.__dict__] +\ + self._main_ns_cache.values() + + if by_name: # Delete by name + for ns in ns_refs: + try: + del ns[varname] + except KeyError: + pass + else: # Delete by object + try: + obj = self.user_ns[varname] + except KeyError: + raise NameError("name '%s' is not defined" % varname) + # Also check in output history + ns_refs.append(self.history_manager.output_hist) + for ns in ns_refs: + to_delete = [n for n, o in ns.iteritems() if o is obj] + for name in to_delete: + del ns[name] + + # displayhook keeps extra references, but not in a dictionary + for name in ('_', '__', '___'): + if getattr(self.displayhook, name) is obj: + setattr(self.displayhook, name, None) + def reset_selective(self, regex=None): """Clear selective variables from internal namespaces based on a specified regular expression. diff --git a/IPython/core/magic.py b/IPython/core/magic.py index cde29a4..d904d28 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -1084,7 +1084,24 @@ Currently the magic system has the following functions:\n""" raise TypeError('regex must be a string or compiled pattern') for i in self.magic_who_ls(): if m.search(i): - del(user_ns[i]) + del(user_ns[i]) + + def magic_xdel(self, parameter_s=''): + """Delete a variable, trying to clear it from anywhere that + IPython's machinery has references to it. By default, this uses + the identity of the named object in the user namespace to remove + references held under other names. The object is also removed + from the output history. + + Options + -n : Delete the specified name from all namespaces, without + checking their identity. + """ + opts, varname = self.parse_options(parameter_s,'n') + try: + self.shell.del_var(varname, ('n' in opts)) + except (NameError, ValueError) as e: + print type(e).__name__ +": "+ str(e) def magic_logstart(self,parameter_s=''): """Start logging anywhere in a session. diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index 671d7ea..f5e3654 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -394,6 +394,28 @@ def test_reset_hard(): nt.assert_equal(monitor, []) _ip.magic_reset("-f") nt.assert_equal(monitor, [1]) + +class TestXdel(tt.TempFileMixin): + def test_xdel(self): + """Test that references from %run are cleared by xdel.""" + src = ("class A(object):\n" + " monitor = []\n" + " def __del__(self):\n" + " self.monitor.append(1)\n" + "a = A()\n") + self.mktmp(src) + # %run creates some hidden references... + _ip.magic("run %s" % self.fname) + # ... as does the displayhook. + _ip.run_cell("a") + + monitor = _ip.user_ns["A"].monitor + nt.assert_equal(monitor, []) + + _ip.magic("xdel a") + + # Check that a's __del__ method has been called. + nt.assert_equal(monitor, [1]) def doctest_who(): """doctest for %who diff --git a/IPython/testing/globalipapp.py b/IPython/testing/globalipapp.py index 162dba9..cee6f2b 100644 --- a/IPython/testing/globalipapp.py +++ b/IPython/testing/globalipapp.py @@ -132,6 +132,16 @@ class ipnsdict(dict): # correct for that ourselves, to ensure consitency with the 'real' # ipython. self['__builtins__'] = __builtin__ + + def __delitem__(self, key): + """Part of the test suite checks that we can release all + references to an object. So we need to make sure that we're not + keeping a reference in _savedict.""" + dict.__delitem__(self, key) + try: + del self._savedict[key] + except KeyError: + pass def get_ipython():