Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 397
Marco Selvi Avatar asked Jun 30 '16 18:06

Marco Selvi


2 Answers

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__]
like image 160
Nizam Mohamed Avatar answered Nov 13 '22 03:11

Nizam Mohamed


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 *
like image 43
MisterMiyagi Avatar answered Nov 13 '22 03:11

MisterMiyagi