Search code examples
pythonimportbuilt-inpython-importlib

Why adding a dot in import_module() avoids ImportError?


Having the following directory tree:

Note: The following behavior is the same with/without the __init__.py files.

top_package
|   top_module.py
|
+---package1
|   |   module1.py
|   |   __init__.py
|
\---package2
    |   module2.py
    |   __init__.py

with top_module.py:

from package1.module1 import CLASS1


a = CLASS1()

module1.py:

from importlib import import_module


class CLASS1:

    def __init__(self):
        # this works:
        foo = getattr(
            import_module(name='..module2', package='package2.'), 'CLASS2')()
        
        # this raises ImportError.
        foo = getattr(
            import_module(name='..module2', package='package2'), 'CLASS2')()

In which the unique difference is that the first uses a dot when defining the package arg. (package='package2.'), while the other doesn't (package='package2').

module2.py:

class CLASS2:

    def __init__(self):
        print(f'hi from: {__name__}')

I am having a hard time understanding why in module1.py the first assignment for foo works, while the next one doesn't, raising ImportError: attempted relative import beyond top-level package.

This error occurs when running top_module.py from its directory:

cd <path-to-top_package>/top_package
python top_module.py

The related info from the docs states

importlib.import_module(name, package=None)

Import a module. The name argument specifies what module to import in absolute or relative terms (e.g. either pkg.mod or ..mod). If the name is specified in relative terms, then the package argument must be set to the name of the package which is to act as the anchor for resolving the package name (e.g. import_module('..mod', 'pkg.subpkg') will import pkg.mod).

which may already be answering my question but I don't get it correctly.


Solution

  • I was wrongly using import_module. The main reason is that if name specifies a relative module, then it is relative w.r.t. the argument package.

    I wrongly thought that name was relative to the module's __file__ value in which import_module is called (in this case module1.py).

    Thereby, the first version:

    import_module(name='..module2', package='package2.')
    

    Imports package2.module2 since ..module2 specifies that the module is in the parent of the package arg. The parent of package2. is package2.

    However,

    import_module(name='..module2', package='package2')
    

    tries to import from the parent of package2, which is unknown, causing ImportError.