Here is my Log book entry.
Code for today.
It has been bugging me for a while now that I seem to hit a Python module / package loading issue just when I think I know how it works. Time to learn and explore just how the basics of the loading system works in Python (version 3 and above).
What are modules and packages?
A module
is any .py source code file. The name of the module is the file name without the extension. For example: the file hello.py is a module and the module name is hello.
A package
can be any directory. Before Python 3.3 a directory must have a file named __init__.py
to be seen as a package. The package name is the directory name.
What is importing?
Importing is the process by which Python code in one module
is made available to Python code in another module
.
An importer
is an object that does both finding and loading of modules.
Single module example
Let us build up our understanding of how this importing process works by first starting with just a single module
.
The project’s root directory is just called project in this example. It contains a file named example.py. Thus this means we have a module named "example".
# Directory structure
project
└── example.py
# example.py
print(f'This is global scope of example.py. __name__ is: {__name__}')
if __name__ == '__main__':
print('This is example.py running')
print(f'My __name__ is: {__name__}')
Let’s see what happens when we tell the interpreter to load our module.
$ python example.py
This is global scope of example.py. __name__ is: __main__
This is example.py running
My __name__ is: __main__
$ python -m example
# Gives the same result
Ok so we can see that when the python interpreter loaded the module it set the __name__
variable to __main__
. It first ran all the code at the global scope.
We then have an if
check to see if the __name__
is __main__
and if so then run more code.
Module importing another module
Add another file named module1.py inside the same directory.
# module1.py
print(f'This is global scope of module1.py. __name__ is: {__name__}')
if __name__ == '__main__':
print('This is module1.py running')
project
├── example.py
└── module1.py
Load and run this new module.
$ python module1.py
This is global scope of module1.py. __name__ is: __main__
This is module1.py running
Modify example.py to import module1 and load example.py.
# example.py
print(f'This is global scope of example.py. __name__ is: {__name__}')
import module1
if __name__ == '__main__':
print('This is example.py running')
print(f'My __name__ is: {__name__}')
$ python example.py
This is global scope of example.py. __name__ is: __main__
This is global scope of module1.py. __name__ is: module1
This is example.py running
My __name__ is: __main__
Notice how the value of __name__
has been changed to module1 when the code from module1.py was loaded and executed as the result of being loaded by another module.
Also take notice that in this case the if __name__ ...
code from module1 was not run (as you would expect).
Key point: The name of the module first loaded and executed by the Python interpreter will be set to __main__
regardless of what the module’s actual name is.
Single package example
Create a new directory named package1 and add a file named __init__.py
project
├── example.py
├── module1.py
└── package1
├── __init__.py
# package1 __init__.py
print(f'This is global scope of package1/__init__.py. __name__ is: {__name__}')
print(f'__package__ is: {__package__}')
Modify example.py
...
print(f'This is global scope of example.py. __name__ is: {__name__}')
print(f'example.py __package__ is: {__package__}')
import module1
import package1
...
Modify module1.py
...
print(f'This is global scope of module1.py. __name__ is: {__name__}')
print(f'module1.py __package__ is: {__package__}')
...
Load and run example.py
$ python example.py
This is global scope of example.py. __name__ is: __main__
example.py __package__ is: None
This is global scope of module1.py. __name__ is: module1
module1.py __package__ is:
This is global scope of package1/__init__.py. __name__ is: package1
__package__ is: package1
This is example.py running
My __name__ is: __main__
Notice how the package name of example.py is None
whereas the package name for module1.py is blank (I am guessing this is an empty string ”).
example.py imported package1 and the loading system found the file named __init__.py
and executed the code. The name of the package is what we would expect (the name of the directory i.e. package1).
What happens if we used python -m
to load and run example.py?
$ python -m example
This is global scope of example.py. __name__ is: __main__
example.py __package__ is:
This is global scope of module1.py. __name__ is: module1
module1.py __package__ is:
This is global scope of package1/__init__.py. __name__ is: package1
__package__ is: package1
This is example.py running
My __name__ is: __main__
Can you spot the difference?
example.py ‘s __package__
is set to None
when you use python example.py
where as it is blank when you use the -m
option to load the module named example.
Wrap up for tonight
I have barely even started scratching the surface of this topic.
Tomorrow I will be diving more into how this module loading process works in Python.