Relative Imports, why the hell is it so hard?

Kay Schluehr kay.schluehr at gmx.net
Tue Mar 31 19:46:27 CEST 2009


On 31 Mrz., 18:48, s4g <rafals... at gmail.com> wrote:
> Hi,
>
> I was looking for a nice idiom for interpackage imports as I found
> this thread.
> Here come a couple of solutions I came up with. Any discussion is
> welcome.
>
> I assume the same file structure
>
> \ App
> | main.py
> +--\subpack1
> | | __init__.py
> | | module1.py
> |
> +--\subpack2
> | | __init__.py
> | | module2.py
>
> When you run main.py all imports relative to \App work fine, so the
> only problem is running a module from within a subpackage as a script.
> I therefore assume we want to run module1.py as a script, which wants
> to import module2.
>
> I hope the following solutions are self-evident
>
> ================= solution 1
> --> in module1.py
> import sys, os
> if __name__ == '__main__':
>     sys.path.append(os.path.normpath(__file__+'/../..'))
>
> import subpack2.module2
>
> ================= solution 2
> --> in subpack1/__init__.py
> import sys, os
>
> _top_package_level = 1   # with current package being level 0
>
> _top_package = os.path.normpath(__file__ + '/..'*(_top_package_level
> +1))
> if _top_package not in sys.path:
>     sys.path.append(_top_package)
>
> --> in module1 or any module in the package, which requires import
> relative to the package top
> import __init__
> import subpack2.module2
>
> ================= solution 3
> --> in package_import.py, somewhere on the PYTHONPATH ( perhaps in
> standard lib ;)
>
> def set_top_package(module, level):
>     _top_package = os.path.normpath(module + '/..'*(level+1))
>     if _top_package not in sys.path:
>         sys.path.append(_top_package)
>
> class absolute_import(object):
>     def __init__(self, module, level):
>         self.level = level
>         self.module = module
>
>     def __enter__(self):
>         sys.path.insert( 0,
>             os.path.normpath(self.module + '/..'*(self.level+1))
>             )
>
>     def __exit__(self, exc_type, exc_val, exc_tb):
>         del sys.path[0]
>
> --> in module1
> import package_import
> package_import.set_top_package(__file__, 1)
> import subpack2.module2
>
> --> or in module1
> import package_import
> with package_import.absolute_import(__file__, 1):
>     import subpack2.module2
>     ...

This and similar solutions ( see Istvan Alberts ) point me to a
fundamental problem of the current import architecture. Suppose you
really want to run a module as a script without a prior import from a
module path:

...A\B\C> python my_module.py

then the current working directory C is added to sys.path which means
that the module finder searches in C but C isn't a known package.
There is no C package in sys.modules even if the C directory is
"declared" as a package by placing an __init__.py file in it. Same
goes of course with B and A. Although the ceremony has been performed
basically correct the interpreter god is not pacified and doesn't
respond. But why not? Because it looks up for *living* imported
packages in the module cache ( in sys.modules ).

I don't think there is any particular design idea behind it. The
module cache is just a simple flat dictionary; a no-brainer to
implement and efficient for look ups. But it counteracts a domain
model. All you are left with is those Finders, Loaders and Importers
in Brett Cannons importlib. Everything remains deeply mysterious and I
don't wonder that it took long for him to work this out.




More information about the Python-list mailing list