Imports at a Distance
Sun 25 June 2017 by Moshe Zadka(Thanks to Mark Williams for feedback and research)
Imagine the following code:
## mymodule.py
import toplevel.nextlevel.lowmodule
def _func():
toplevel.nextlevel.lowmodule.dosomething(1)
def main():
_func()
Assuming the toplevel.nextlevel.module
does define a function dosomething,
this code seems to work just fine.
However, imagine that later we decide to move _func
to a different module:
:
.. code:
# utilmodule.py
import toplevel
def _func():
toplevel.nextlevel.lowmodule.dosomething(1)
This code will probably still work,
as long as at some point, before calling _func,
we import mymodule.
This introduces a subtle action-at-a-distance:
the code will only stop working when we remove the import
from mymodule -- or any other modules
which import lowmodule.
Even unit tests will not necessarily catch the problem,
depending on the order of imports of the unit tests.
Static analyzers, like pylint and pyflakes,
also cannot catch it.
The only safe thing to do is to eschew this import style completely,
and always do from toplevel.nextlevel import lowmodule.
Addendum
Why is this happening?
Python package imports are a little subtle.
import toplevel
Does three things:
- (Once only) Creates a
toplevelentry insys.modules - (Once only) Executes
toplevel/__init__.pyinside the namespace oftoplevel - Creates a variable called
topleveland assigns it the module.
The things marked "once only" will only happen the first time
toplevel is imported.
import toplevel.nextlevel
Does the same three things (with :code:`toplevel`) as well as:
- (Once only) Creates a
toplevel.nextlevelentry insys.modules - (Once only) Executes:code:toplevel/nextlevel/__init__.py inside the namespace of
toplevel.nextlevel - (Once only) Creates a variable
nextlevelin the namespace oftoplevel, and binds thetoplevel.nextlevelmodule to it.
The third one is the most interesting one --
note that the first time toplevel.nextlevel is imported,
a nextlevel variable is created in the namespace of toplevel,
so that every subsequent place that imports toplevel can
access nextlevel for "free".