How to reference to the top-level module in Python inside a package?

Question:

In the below hierachy, is there a convenient and universal way to reference to the top_package using a generic term in all .py file below? I would like to have a consistent way to import other modules, so that even when the “top_package” changes name nothing breaks.

I am not in favour of using the relative import like “..level_one_a” as relative path will be different to each python file below. I am looking for a way that:

  1. Each python file can have the same import statement for the same module in the package.
  2. A decoupling reference to “top_package” in any .py file inside the package, so whatever name “top_package” changes to, nothing breaks.

    top_package/
      __init__.py
      level_one_a/
        __init__.py
        my_lib.py
        level_two/
          __init__.py
          hello_world.py
      level_one_b/
        __init__.py
        my_lib.py
      main.py
    
Asked By: hllau

||

Answers:

This should do the job:

top_package = __import__(__name__.split('.')[0])

The trick here is that for every module the __name__ variable contains the full path to the module separated by dots such as, for example, top_package.level_one_a.my_lib. Hence, if you want to get the top package name, you just need to get the first component of the path and import it using __import__.

Despite the variable name used to access the package is still called top_package, you can rename the package and if will still work.

Answered By: jcollado

I believe #2 is impossible without using relative imports or the named package. You have to specify what module to import either by explicitly calling its name or using a relative import. otherwise how would the interpreter know what you want?

If you make your application launcher one level above top_level/ and have it import top_level you can then reference top_level.* from anywhere inside the top_level package.

(I can show you an example from software I’m working on: http://github.com/toddself/beerlog/)

Answered By: tkone

You could use a combination of the __import__() function and the __path__ attribute of a package.

For example, suppose you wish to import <whatever>.level_one_a.level_two.hello_world from somewhere else in the package. You could do something like this:

import os
_temp = __import__(__path__[0].split(os.sep)[0] + ".level_one_a.level_two.hello_world")
my_hello_world = _temp.level_one_a.level_two.hello_world

This code is independent of the name of the top level package and can be used anywhere in the package. It’s also pretty ugly.

Answered By: srgerg

Put your package and the main script into an outer container directory, like this:

container/
    main.py
    top_package/
        __init__.py
        level_one_a/
            __init__.py
            my_lib.py
            level_two/
                __init__.py
                hello_world.py
        level_one_b/
            __init__.py
            my_lib.py

When main.py is run, its parent directory (container) will be automatically added to the start of sys.path. And since top_package is now in the same directory, it can be imported from anywhere within the package tree.

So hello_world.py could import level_one_b/my_lib.py like this:

from top_package.level_one_b import my_lib

No matter what the name of the container directory is, or where it is located, the imports will always work with this arrangement.

But note that, in your original example, top_package it could easily function as the container directory itself. All you would have to do is remove top_package/__init__.py, and you would be left with efectively the same arrangement.

The previous import statement would then change to:

from level_one_b import my_lib

and you would be free to rename top_package however you wished.

Answered By: ekhumoro

This works from within a library module:

import __main__ as main_package
TOP_PACKAGE = main_package.__package__.split('.')[0]
Answered By: Apalala
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.