Marco Selvi
Marco Selvi

Reputation: 171

In Python, is it possible to expose modules from subpackages at package level?

I have the following conundrum. I'm trying to expose some modules from a subpackage of a package at the parent package level.

The folder structure is the essentially like this:

script.py
package/
    __init__.py
    module1.py
    subpackage/
        __init__.py
        submodule1.py
        submodule2.py

In the script.py file I currently have to write

from package.subpackage.submodule1 import foo

if I want to import something from the submodule1.py file, but I would like to be able to expose the files submodule1.py and submodule2.py at package level, so that all my imports can look like

from package.module1 import bar
from package.submodule1 import foo
from package.submodule2 import goo

Note that I don't want to expose bar, foo and goo at package level, i.e. not

from package import bar
from package import foo

because the separation between modules is still important in my case.

Is this even possible? Is there a trick in the __init__.py file to do so?

Thanks!

Upvotes: 14

Views: 7573

Answers (2)

Nizam Mohamed
Nizam Mohamed

Reputation: 9220

Yes It's possible.
Let's see what happens step by step;

  1. When you do from package.submodule1 import foo, sys.modules is checked if it has package.
  2. If not found package/__init__.py is run and loaded
  3. Again sys.modules is checked for package.submodule1
  4. If not found, package/submodule1.py is checked for existence.

We can make step 3 pass by making an entry in sys.modules for package.submodule1 in step 2.

package/__init__.py

import sys
from .subpackage import submodule1
from .subpackage import submodule2

for module in (submodule1, submodule2):
    full_name = '{}.{}'.format(__package__, module.__name__.rsplit('.')[-1])
    sys.modules[full_name] = sys.modules[module.__name__]

Upvotes: 6

MisterMiyagi
MisterMiyagi

Reputation: 50086

For your purpose, python modules are just namespaces. That is, everything in the globals of a module can be imported and used as module.thingy. You may have noticed that in many modules, you also find some builtins. For example, logging.os is just the regular os module.

So, in your package (package/__init__.py) import whatever you wish, and bind it to the name you want to expose it as.

# package/__init__.py
# import package.module1 as module1  # one *can* do this, but its at best without benefit (see comments)
import package.subpackage.submodule1 as submodule1
import package.subpackage.submodule2 as submodule2

This allows to do import package.submodule1 and import package.submodule1 as foo.

Note that this simple way will not allow you to do from package.submodule1 import bar. For this, you need an actual dummy module.

# package/submodule1/__init__.py
from package.subpackage.submodule1 import *

Upvotes: 13

Related Questions