classification
Title: importlib reload fails for module supplied as argument to command line
Type: behavior Stage:
Components: Versions: Python 3.5
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: Nosy List: brett.cannon, j1o1h1n, ncoghlan, r.david.murray
Priority: normal Keywords:

Created on 2017-01-08 11:43 by j1o1h1n, last changed 2017-01-09 02:17 by ncoghlan. This issue is now closed.

Files
File name Uploaded Description Edit
reloader.py j1o1h1n, 2017-01-08 11:43
Messages (4)
msg284982 - (view) Author: John Lehmann (j1o1h1n) * Date: 2017-01-08 11:43
In testing the py3 port for the web framework web.py, I found a limitation of importlib.reload.

A module that was loaded via the command line cannot be reloaded with importlib.reload.

The basic mode of operation for web.py is to create a web application as a single module, say, "app.py" and run this with "python app.py".

When in development mode, on each page view, the modification time for each file backing each module is checked for changes.  If the file has changed, the file is reloaded.

This allows for an iterative development mode familiar to web developers since the glory days of writing VB pages in ASP.

The problem occurs when the file is loaded directly, or with "-m".

For example with the attached file:

  $ python reloader.py
  Traceback (most recent call last):
    File "reloader.py", line 31, in <module>
      reload_module("__main__")
    File "reloader.py", line 28, in reload_module
      importlib.reload(module)
    File "/usr/local/var/pyenv/versions/3.5.2/lib/python3.5/importlib/__init__.py", line 166, in reload
      _bootstrap._exec(spec, module)
    File "<frozen importlib._bootstrap>", line 607, in _exec
  AttributeError: 'NoneType' object has no attribute 'name'

And with -m:

  $ python -m reloader
  Traceback (most recent call last):
    File "/usr/local/var/pyenv/versions/3.5.2/lib/python3.5/runpy.py", line 184, in _run_module_as_main
      "__main__", mod_spec)
    File "/usr/local/var/pyenv/versions/3.5.2/lib/python3.5/runpy.py", line 85, in _run_code
      exec(code, run_globals)
    File "reloader.py", line 31, in <module>
      reload_module("__main__")
    File "reloader.py", line 28, in reload_module
      importlib.reload(module)
    File "/usr/local/var/pyenv/versions/3.5.2/lib/python3.5/importlib/__init__.py", line 147, in reload
      raise ImportError(msg.format(name), name=name)
  ImportError: module reloader not in sys.modules

Note that there are two different error messages, neither of which is great.

There are workarounds, but given that this works in previous version of python, it might be better to fix it.  Or to have an explicit error message that describes the problem more precisely:

  "Cannot reload __main__"
msg284983 - (view) Author: John Lehmann (j1o1h1n) * Date: 2017-01-08 11:47
I may not have been clear as to how the problem seems when working with a web.py application:

* visit the web page, see something that needs fixing
* make the required change to app.py
* reload the webpage
* error occurs
msg285003 - (view) Author: Brett Cannon (brett.cannon) * (Python committer) Date: 2017-01-08 19:00
The error from using -m stems from runpy not aliasing the module as both "__main__" and "reloader". I suspect the reason is that having a module under two names gets really messy because if you update just one of those modules you leave the  other one around and then you have skewed the results.

As for your use-case, be aware that importlib.reload() is not designed for it. You will still have objects floating around in the interpreter that are using the original module and so you're not going to get all instances of objects automatically updated to the new code. The only way to guarantee usage of the new code is to restart the interpreter.

I get why you want this, John, but Python is simply not structured to support what you're after. Closing as "won't fix".
msg285015 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2017-01-09 02:17
Since reload re-uses the existing namespace, having two names is less messy when they're just aliases for the same module object (it still has all the usual cache validity problems of any reload operation, but it doesn't have the extra challenges of two different module objects derived from the same source code).

As such, with -m, adding:

    import __main__
    sys.modules[__main__.__spec__.name] = __main__

is sufficient to get this working again, and will work for any of the PEP 451 based versions (i.e. 3.4+). (I'm open to an RFE to get runpy to do this by default in 3.7+ - it's an idea that has come up several times, and I think it will ultimately be less surprising than the current behaviour of allowing two entirely distinct copies of the module to be loaded)

The direct execution case is a bit different, as that's genuinely missing a __spec__ entry, and needs to be told how to reload itself:

    import __main__
    main_file = __main__.__file__
    main_name = os.path.splitext(os.path.basename(main_file))[0]
    __main__.__spec__ = importlib.util.spec_from_file_location(main_name, main_file)
    sys.modules[__main__.__spec__.name] = __main__

The two cases can be distinguished at runtime by whether or not __main__.__spec__ was already set.

Since this isn't really something we encourage people to do in general, but does remain possible for frameworks that want to support live reloading of __main__, I'm OK with requiring that extra framework-provided scaffolding in 3.4+.
History
Date User Action Args
2017-01-09 02:17:37ncoghlansetmessages: + msg285015
2017-01-08 19:00:30brett.cannonsetstatus: open -> closed

nosy: + ncoghlan
messages: + msg285003

resolution: wont fix
2017-01-08 11:47:10j1o1h1nsetmessages: + msg284983
2017-01-08 11:43:51j1o1h1ncreate