You might have seen or written your own import statements while writing Python programs. Have you ever wondered about the mechanism behind the import statement? Wondered how it effortlessly fetches the packages you need and helps you use the functionalities present in those packages?
If you haven't, its time you ponder over this question and continue reading this post to understand what actually happens when the import statement is executed.
The process of gaining access to the code in module A and using it in module B is known as importing (that module A into environment module B). This is done with the help of the import
statement.
When an import
statement is executed, (say import module_1) the built-in method __import__()
is called.
Note: It is not a hard and fast rule that only the __import__
method has to be called.
Suppose, importlib.import_module()
is used. Then, instead of calling the __import__
method, customized import methods are implemented.
What if I call the __import__
method directly?
This can be done, but it might lead to certain consequences, which include, but not limited to, import of parent packages, updating of different cache (this could include the sys.modules). In addition to this, the call to __import__
method will not bind the name of the module to the name in the local namespace. It only searches for the module and if the module is found, it creates a module object and initializes it. In case the module is not found, it raises a ModuleNotFoundError.
Many techniques can be used to search for a module in Python, and these can be modified and customized according to the implementation requirements.
Below is an example that shows what happens when a module is not found:
import tensorflow
Following will be the output,
Traceback (most recent call last):
File "<ipython-input-1-d6579f534729>", line 1, in <module>
import tensorflow
ModuleNotFoundError: No module named 'tensorflow'
The import statement brings together two operations, i.e searching, and binding.
The name of the module written after the import statement is searched for in that system, and the result of this search is integrated with a name in that local scope. During searching operation, the __import__
function is called by passing relevant parameters to it. Then a module object is created and initialized. The value returned by the __import__
function is used to bind it to the namespace in the local scope for that part of the code.
Consider the below statement,
import module_1, module_2
This import statement is executed as if there were 2 import statements used to import the module_1 and module_2 separately. The modules are searched for, loaded, initialized and then bound with the local namespace like a reference.
Suppose module_1 and module_2 are top-level modules, i.e they are both parent modules that contain many more methods and implemented functionalities, in such cases, the top-level module's name is bound with the local namespace, like a reference.
In case module_1 and module_2 are not top-level modules, the parents (or top-level package) of these 2 modules are bound to the local namespace like a reference. In addition to this, the modules need to be accessed with the help of their parent module and the dot operator.
For example,
import numpy as np
np.square()
Numpy module has been imported with the alias name np and one of the methods present inside the np module is square
. It is accessed using the dot operator by binding the parent module with the square
method.
Consider the below statement:
import module_1 as m1
Here the keyword as helps give an alias name to the module_1 and m1 is bound to the local namespace (which actually refers to module_1)
Consider the below statement:
from numpy import square
When the from
keyword is used, the module followed by the from
keyword is searched for, loaded and initialized. Here the square
method or attribute of the Numpy package needs to be accessed. Hence the square
attribute is searched for in the module. If it is not found, a submodule with that same name is searched for, in which again the square attribute is searched for. If not found, an ImportError is raised. Otherwise, it is stored like a reference in the local namespace.
Below is a demonstration of ImportError:
from numpy import squar
For the code above, we will get the following output:
Traceback (most recent call last):
File "<ipython-input-6-774fbf55d7b8>", line 1, in <module>
from numpy import squar
ImportError: cannot import name 'squar' from 'numpy' (C:\ProgramData\Anaconda3\lib\site-packages\numpy\__init__.py)
Summary:
import module_1
: This statement binds module_1 to a local namespace.
import module_1.module_2.module_3
: This statement imports module_1.module_2.module_3 and binds module_1 with the local namespace like a reference and uses module_3.
import module_1.module_2.module_3 as mod_one
: This statement imports module_1.module_2.module_3 and binds mod_one to local namespace.
from module_1.module_2 import module_3
: This statement imports module_3 from module_1.module_2 and binds it to the local namespace
from module_1 import attr_1
: This statement imports attr_1 from the module module_1 and binds it to the local namespace.
Conclusion
In this post, we understood how the import statement could be used to import modules and the workings behind it.