Daniel Goldfarb
Daniel Goldfarb

Reputation: 7714

access to import statement from within __init__.py (or how to not run __init__.py when importing submodule)

My package looks something like this:

/mypackage
|-- __init__.py
|-- moduleA.py
|-- moduleB.py
|-- oldModule.py

And __init__.py looks something like this:

from mypackage.moduleA import abc
from mypackage.moduleB import jkl

This allows users to simply code as follows:

import mypackage as mpkg

mpkg.abc()
mpkg.jkl()

By default (using the above import) users have no access to functions in oldModule.py.
This is because 95% of users don't need that stuff (it's old).


For the 5% of users who need functions from the oldModule, they can write:

from mypackage.oldModule import xyz
xyz()

Many of these "oldModule" users don't need access to the functions imported by __init__.py

The problem is, when a user only does from mypackage.oldModule import xyz
it is clear the the entire __init__.py is also being run.

Is there any way that I can either

Thanks.

Upvotes: 0

Views: 266

Answers (1)

Blckknght
Blckknght

Reputation: 104752

There's no way to do exactly what you're asking for. When you import some_package.some_module, the package always gets fully loaded before the submodule does.

But there might be some ways to mitigate the issues this causes in your case. If you want to avoid importing the moduleA and moduleB submodules when a user is looking for oldModule, you can perhaps make their loading lazy, rather than eager.

Starting in Python 3.7, PEP 562 enables a module to have a function named __getattr__ which will work like a method of the same name defined in a class. When an attribute of the module is looked up but not found, the __getattr__ function will be called with the name.

So in your __init__.py file, you could do:

# from mypackage.moduleA import xyz   don't do these imports unconditionally any more
# from mypackage.moduleB import abc

def __getattr__(name):
    global xyz, abc
    if name == 'xyz':
        from .moduleA import xyz
        return xyz
    elif name == 'abc':
        from .moduleB import abc
        return abc
    raise AttributeError(f"module {__name__} has no attribute {name}")

If you have a lot more names you need to protect this way (not just two), you may want to write more general code that can process a list of names (probably one per sub-module) and import them properly using importlib and writing directly into the package's __dict__. That will be more convenient than trying to use global statements and writing a separate if branch for each name.

Upvotes: 1

Related Questions