What you did certainly works, although there are several worthwhile changes to make.
First, a note about importing from packages: importing a module is semantically distinct from accessing something in a module, even though from xml import sax
and from datetime import date
are syntactically equivalent. It's impossible to import only part of a module, so that
import datetime
datetime.date.today() # OK: date is a class
is guaranteed to work. However, it is possible to import a package but not the modules it contains. This is a good thing for efficiency, but it does mean that
import xml
xml.sax.parse(...) # AttributeError: 'module' object has no attribute 'sax'
is an error. Unfortunately, such errors often go uncaught because some other code has already imported sax
, making it available to any other code that imports xml
. (The word "from" in from xml import sax
is referring to the complete package on disk, not the module object xml
— on which it stores the new module as an attribute!)
As an aside, note that your example of os.path
is an abberation: writing
import os
os.path.isfile(...)
works, but only because os.path
is not actually a module but an alias for one of posixpath
, ntpath
, etc. (It then gets installed in sys.modules
to allow import os.path
as if it were a normal module.)
As such, for a package there is a set of public modules that the user must be aware of (because they must be imported by name to be available); the rest are internal modules that the package loads itself when necessary. If a package contains no public modules, it is irrelevant to the user that it is a package (for example, importlib
with its one public function is actually implemented as a package for forward compatibility with Python 3).
Now for the suggestions:
- Implicit relative imports are deprecated: write
from . import pack1
instead of just import pack1
in sandbox/__init__.py
, for instance.
- The
from plane import plane
(or from .plane import plane
, following the above point) is problematic because it overwrites the reference to the module plane.py
with a reference to the function. Instead:
- Define the user-visible entry points (like
plane()
) directly in their package's __init__.py
, importing internal functions from private modules as needed, or
- Rename the module (to
plane_module.py
or so) to avoid the collision.
However, it's not generally a good idea to have a package automatically import its public modules anyway: it forces the client to pay for loading unused parts of the package, and it blurs the distinction between public modules and simple nested names. Instead, write client code like
import sandbox.pack1.fly
print sandbox.pack1.fly.plane(3) # the same line you had
or
from sandbox.pack1 import fly
print fly.plane(3)
if you want to avoid repeating sandbox.pack1
.
It is often suggested that __init__.py
be entirely empty, and in Python 3.3 it became possible to define packages without any __init__.py
at all (which by necessity "makes it empty"). This policy does more than the "no automatic import" suggestion in that it precludes loading things from private modules (like plane
).
There are sometimes good reasons to have a non-empty __init__.py
; for example, it allows reorganzing an existing module into a package without breaking its clients. I personally see no reason to especially restrict its contents; for further discussion see What is __init__.py for?.