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
toplevel
entry insys.modules
- (Once only) Executes
toplevel/__init__.py
inside the namespace oftoplevel
- Creates a variable called
toplevel
and 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.nextlevel
entry insys.modules
- (Once only) Executes:code:toplevel/nextlevel/__init__.py inside the namespace of
toplevel.nextlevel
- (Once only) Creates a variable
nextlevel
in the namespace oftoplevel
, and binds thetoplevel.nextlevel
module 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".