Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Errors while dynamic imports using importlib in python3

I have been trying to use importlib with python3 (3.6).

Directory structure

main.py

#Note: I will only modify line 4 that uses importlib
import importlib
if __name__ == '__main__':
    print("In main.py")
    hello = importlib.import_module('hello', package='./')
    print("Loaded hello.py")
    hello.hello()

hello.py

def hello():
    print('Hello world')

folder/hello.py

def hello():
    print('Hello world in folder')

Observations

If I do

hello = importlib.import_module('hello', package='./') or

hello = importlib.import_module('hello')

It imports hello.py from the root folder and prints hello world.

If I do

hello = importlib.import_module('folder.hello')

It imports folder/hello.py from the root folder and prints hello world in folder.

But if I do

hello = importlib.import_module('hello', package='folder') or

hello = importlib.import_module('hello', package='./folder')

It gives error

Traceback (most recent call last):
  File "main.py", line 4, in <module>
    hello = importlib.import_module('hello', package='./folder')
  File "/usr/lib/python3.6/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 994, in _gcd_import
  File "<frozen importlib._bootstrap>", line 971, in _find_and_load
  File "<frozen importlib._bootstrap>", line 953, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'hello'

Problem

I am not sure what is going on here. I am pretty sure there is something wrong with my understanding of python modules and packages. Can someone explain why this is the expected behavior?

like image 476
Umang Gupta Avatar asked May 11 '18 02:05

Umang Gupta


People also ask

What is importlib in Python 3?

New in version 3.1. The purpose of the importlib package is two-fold. One is to provide the implementation of the import statement (and thus, by extension, the __import__ () function) in Python source code. This provides an implementation of import which is portable to any Python interpreter.

Is there a way to do Dynamic imports in Python?

However, the whole import machinery is available in the importlib package, and this allows you to do your imports more dynamically. The following script asks the user for the name of a module, imports that module, and prints its docstring: import_module () returns a module object that you can bind to any variable.

Why can’t I import a module in Python?

Since there are no finders, Python can’t find or import new modules. However, Python can still import modules that are already in the module cache since it looks there before calling any finders. In the example above, importlib was already loaded under the hood before you cleared the list of finders.

How to dynamically loading modules or classes in Python?

Dynamically Loading Modules or Classes 1 By using __import__ () method: __import__ () is a dunder method (methods of class starting and ending with double... 2 Using the imp module: Modules can be imported dynamically by the imp module in python. The example below is a... More ...


2 Answers

If the first argument, the module to be imported is an absolute module reference ( has no leading .), the seond argument is completely ignored.

To import a module a relative to another module b, you have to use

a = importlib.import_module('.a', package='b')

In your case, this should work

hello = importlib.import_module('.hello', package='folder')

As a rule of thumb, import package should work if you want to use package as second argument.

from package import module

then becomes

importlib.import_module(module, package)
like image 183
Mahesh Avatar answered Nov 04 '22 21:11

Mahesh


@Mahesh's answer is 100% correct and spot-on, but I guess we need to go one level deep to make you understand it better

Below is the code for import_module

def import_module(name, package=None):
    """Import a module.

    The 'package' argument is required when performing a relative import. It
    specifies the package to use as the anchor point from which to resolve the
    relative import to an absolute import.

    """
    level = 0
    if name.startswith('.'):
        if not package:
            msg = ("the 'package' argument is required to perform a relative "
                   "import for {!r}")
            raise TypeError(msg.format(name))
        for character in name:
            if character != '.':
                break
            level += 1
    return _bootstrap._gcd_import(name[level:], package, level)

You can see if the name doesn't start with . then if part doesn't get executed. You just have return _bootstrap._gcd_import(name[level:], package, level) which gets executed with level=0 as the value

Now let's get into that function, which has below code

def _gcd_import(name, package=None, level=0):
    """Import and return the module based on its name, the package the call is
    being made from, and the level adjustment.

    This function represents the greatest common denominator of functionality
    between import_module and __import__. This includes setting __package__ if
    the loader did not.

    """
    _sanity_check(name, package, level)
    if level > 0:
        name = _resolve_name(name, package, level)
    return _find_and_load(name, _gcd_import)

Again in this it just executes _find_and_load(name, _gcd_import), now because level is 0 from our previous code, the package parameter is not being passed or used at all by _find_and_load method. Now you can easily verify this by running below

import importlib
hello = importlib.import_module('hello', package='IAmNotAfolder')
hello.hello()

And it will print Hello World from the base hello.py

So as you can see the package parameter is not used at all when the name doesn't start with ., which is for relative imports. That is why you get an error No module named 'hello' because it is trying to import hello.py from base folder irrespective of what you have in package.

Hope this answer make it easier for you to understand what is happening behind the scenes

like image 31
Tarun Lalwani Avatar answered Nov 04 '22 22:11

Tarun Lalwani