3

I've deleted a (package builtin) function on ipython:

Python 3.6.4 |Anaconda custom (64-bit)| (default, Jan 16 2018, 10:22:32) [MSC v.1900 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import math

In [2]: math.cos(0)
Out[2]: 1.0

In [3]: del math.cos

In [4]: math.cos(0)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-9cdcc157d079> in <module>()
----> 1 math.cos(0)

AttributeError: module 'math' has no attribute 'cos'

OK. But how do I reload the function? This didn't help:

In [5]: import importlib

In [6]: importlib.reload(math)
Out[6]: <module 'math' (built-in)>

In [7]: math.cos(0)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-9cdcc157d079> in <module>()
----> 1 math.cos(0)

AttributeError: module 'math' has no attribute 'cos'
Aguy
  • 7,851
  • 5
  • 31
  • 58

2 Answers2

4

The above code works for me in Python 3.4 on Windows but the documentation for 3.6 states:

Beware though, as if you keep a reference to the module object, invalidate its cache entry in sys.modules, and then re-import the named module, the two module objects will not be the same. By contrast, importlib.reload() will reuse the same module object, and simply reinitialise the module contents by rerunning the module’s code.

(so maybe I was only "lucky")

so what is pretty sure to work is:

import math,sys
del math.cos
del math
sys.modules.pop("math")   # remove from loaded modules
import math
print(math.cos(0))

It still works, and you don't even need reload. Just remove from cache & import again.

As noted in comments, using reload also works, but you need to update the module reference given by reload, not just reuse the same old one with the cos entry missing:

import math,sys
del math.cos
import importlib
math = importlib.reload(math)
print(math.cos(0))
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • Ow, you can use reload() no problem. Just don't del math, but do: math = reload(math). Whether you do or don't need to remove it from sys.modules first I don't know for Py3. On Py2.7 it is not necessary for most modules. Your example with math works just fine without manually modifying the cache. – Dalen Feb 15 '18 at 15:27
  • that sounds cleaner, yes. Edited. – Jean-François Fabre Feb 15 '18 at 15:28
  • BTW, although the module's address in cache (its reference) doesn't change after reload(), the module in the cache does appear to be reloaded. E.g. sys.modules["math"].cos, after del math.cos will raise AttributeError, but after math = reload(math), sys.modules["math"].cos will be back again. So cache is refreshed. At least in Py27 it is. – Dalen Feb 15 '18 at 15:34
  • @Jean-François Fabre, on python 3.6.4 the reload method does not work for me. Is this a bug? The sys.modules.pop method, on the other hand. does work! – Aguy Feb 15 '18 at 15:38
  • I'm glad that I provided both solutions then. I don't have python 3.6 here to test. – Jean-François Fabre Feb 15 '18 at 15:43
  • 1
    Oh, no, this is just nice and easy. Imagine reloading whole module that was imported using 'from' statement. That's a real mess. Oh, and, yes, using reload() will work, but it will reload only your module. Modules that your module is importing won't be reloaded as long as they are in cache. So don't be surprised if you reload your module after you changed something in its submodule and the change is not reflected. – Dalen Feb 15 '18 at 15:43
  • @Aguy : You didn't forget that reload() is moved to importlib module in Python 3. Didn't you? – Dalen Feb 15 '18 at 15:48
  • @Dalen, No I did not. Besides, I copy pasted the code in the answer above. – Aguy Feb 15 '18 at 15:54
  • @Aguy : Yeah, you're right, doesn't work on Python 3.5 either. I'll add my answer here with a function that will work for 2 and 3. I sniffed out the way to do it, but it would be painful to type everything whenever you need to reload. – Dalen Feb 15 '18 at 16:06
  • @Aguy I asked a [follow-up question](https://stackoverflow.com/questions/48813320/should-imprtlib-reload-restore-a-deleted-attribute-in-python-3-6) because you are right that this is unexpected behavior. – ely Feb 15 '18 at 17:45
  • @Aguy : I have no idea why importlib.reload() acts oddly, but while developing the module I posted as an answer I saw that Py 3 changed a bit the way module caching works. But it should work anyway. Well, whomever needs to reload something can use my module until importlib.reload() is fixed. – Dalen Feb 15 '18 at 23:10
0

OK people, I wrote a little module to solve this confusion. I sincerely doubt that it is buggless. So improvements are welcome.

This module works nicely both on Py 2 and 3. Tried on Python 2.7 and Python 3.5.



# rld.py --> the reloading module

import sys

__all__ = ["reload"]

try:
    # Python 2:
    _reload = reload
    from thread import get_ident
    def reload (module):
        if isinstance(module, basestring):
            module = sys.modules[module]
        if module.__name__==__name__:
            raise RuntimeError("Reloading the reloading module is not supported!")
        print ("Reloading: %s" % module.__name__)
        # Get locals() of a frame that called us:
        ls = sys._current_frames()[get_ident()].f_back.f_locals
        # Find variables holding the module:
        vars = [name for name, mod in ls.iteritems() if mod==module]
        if len(vars)==0:
            print ("Warning: Module '%s' has no references in this scope.\nReload will be attempted anyway." % module.__name__)
        else:
            print("Module is referenced as: %s" % repr(vars))
        # Reload:
        m = _reload(module)
        for x in vars:
            ls[x] = m
        print("Reloaded!")
except NameError:
    # Python 3:
    from _thread import get_ident
    def reload (module):
        if isinstance(module, str):
            module = sys.modules[module]
        if module.__name__==__name__:
            raise RuntimeError("Reloading the reloading module is not supported!")
        print ("Reloading: %s" % module.__name__)
        # Get locals() of a frame that called us:
        ls = sys._current_frames()[get_ident()].f_back.f_locals
        # Find variables holding the module:
        vars = [name for name, mod in ls.items() if mod==module]
        if len(vars)==0:
            print ("Warning: Module '%s' has no references in this scope.\nReload will be attempted anyway." % module.__name__)
        else:
            print("Module is referenced as: %s" % repr(vars))
        # Dereference all detected:
        for x in vars:
            del ls[x]
        del sys.modules[module.__name__]
        # Reimport:
        m = __import__(module.__name__)
        # Rebind to old variables:
        for x in vars:
            ls[x] = m
        # Remap references in the old module
        # to redirect all other modules that already imported that module:
        for vname in dir(m):
            setattr(module, vname, getattr(m, vname))
        print("Reloaded!")

>>> # Usage:
>>> from rld import * # Always import it first - just in case
>>> import math
>>> math.cos
<built-in function cos>
>>> del math.cos
>>> math.cos
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'cos'
>>> reload(math)
Reloading: math
Module is referenced as: ['math']
Reloaded!
>>> math.cos
<built-in function cos>
>>> #-------------------------
>>> # This also works:
>>> import math as quiqui
>>> alias = quiqui
>>> del quiqui.cos
>>> quiqui.cos
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'cos'
>>> alias.cos
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'cos'
>>> reload("math")
Reloading: math
Module is referenced as: ['alias', 'quiqui']
>>> quiqui.cos
<built-in function cos>
>>> alias.cos
<built-in function cos>

Dalen
  • 4,128
  • 1
  • 17
  • 35