Search code examples
pythonpython-importpython-importlib

Can you use Python relative imports to import the __init__ file of the current module under a specific name?


Imagine I have a module with two files, like this:

mymodule
 |-- __init__.py
 `-- submodule.py

mymodule/__init__.py contains:

SOME_CONSTANT_ONE = 1
SOME_CONSTANT_TWO = 2
SOME_CONSTANT_THREE = 3
...
SOME_CONSTANT_ONE_HUNDRED = 100

def initialize():
    pass # do some stuff

def support_function():
    pass # something that lots of other functions might need

I already know that I can use a relative import to bring in specific objects from the __init__.py file, like this:

submodule.py:

from . import initialize, support_function

def do_work():
    initialize() # initialize the module
    print(support_function()) # do something with the support function

But now what I want to know is if I can import all of the constants from the __init__.py file, but simultaneously have them appear in a namespace.

What won't work (what I've tried/considered):

  • import mymodule as outer_module works, since the import system already has knowledge of where the module is. However, if I ever need to change the name of the outer module, that code will break.
  • Doing import . as outer_module doesn't work.
  • Doing from . import * does work but puts all of the objects in __init__.py in the current namespace rather than in the sub-namespace.
  • Doing from . import SOME_CONSTANT_ONE as outer_constant_1, SOME_CONSTANT_TWO as outer_constant_2, SOME_CONSTANT_THREE as outer_constant_3, ... is ugly and won't bring in any new constants should they be defined later on in __init__.py.

What I really want is something like this:

submodule.py:

SOME_CONSTANT_ONE = "one!" # We don't want to clobber this.

import . as outer_module # this does not work, but it illustrates what is desired.

def do_work():
    print(SOME_CONSTANT_ONE)              # should print "one!"
    print(outer_module.SOME_CONSTANT_ONE) # should print "1"

I know that I could move all of the constants to a constants.py file and then I should be able to import it with from . import constants (as something) but I'm working on existing code and making that change would require a lot of refactoring. While that's not a bad idea, I'm wondering, given that Python does have a way to import individual objects, and also to import the whole module by name to an explicit name, if I can maybe do something with importlib to accomplish importing everything from __init__.py into a namespace?


Solution

  • The loader sets __package__ which you can use:

    import sys
    
    SOME_CONSTANT_ONE = "one!" # We don't want to clobber this.
    
    outer_module = sys.modules[__package__]
    
    def do_work():
        print(SOME_CONSTANT_ONE)              # should print "one!"
        print(outer_module.SOME_CONSTANT_ONE) # should print "1"
    

    This is precisely the attribute from which relative imports are based. See PEP 366 for details.

    However, I really think the backwards-compatible refactoring which the other answer suggests is probably the better approach here.