As a Python developer, you must have had an encounter with the following code. If you do not yet know what is for, or why where does __name__
comes from, this guide will try to explain that, and more!
if __name__ == '__main__':
// do something
print("Hello, John")
To understand the need for this code, we need to delve into the python module system, special variables, and the execution of Python applications.
Python’s module system
Python, like any other programming files, allows us to split code into multiple files. Then, we can import other files, when we need code present in those files. In the python world, each file is a module, with its defined variables, classes, or other imports.
For example, let us create a simple file named triangles.py
with a class Triangle and a function area:
class Triangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(tri):
return tri.width * tri.height / 2
In an interactive python terminal, we can import the triangles.py
module and inspect its contents with the builtin dir
function. We can see that both Triangle and area are exported:
>>> import triangles
>>> dir(triangles)
['Triangle', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'area']
>>> t = triangles.Triangle(1, 2)
>>> triangles.area(t)
1.0
We can also import files in sub-directories. For example, if the file triangles.py
was inside a directory geometry, we could import it with import geometry.triangles
instead:
>>> import geometry.triangles
>>> dir(geometry.triangles)
['Triangle', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'area']
__name__ and __package__ Python Variables
You must already see that the special variables __name__
and __package__
are in the exports of the triangles module. And indeed, you can inspect their value:
>>> # for the triangles.py module
>>> import triangles
>>> triangles.__name__
'triangles'
>>> triangles.__package__
''
>>> # for the geometry/triangles.py module
>>> import geometry.triangles
>>> geometry.triangles.__name__
'geometry.triangles'
>>> geometry.triangles.__package__
'geometry'
>>> # for any other module, for example numpy
>>> import numpy
>>> numpy.__name__
'numpy'
>>> numpy.__package__
'numpy'
Both variables are defined for each module at import and can be read by the code inside the module. So, if we add a function the file triangles.py
that prints the __name__
variable, we can check what is the value inside the module:
class Triangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(tri):
return tri.width * tri.height / 2
def read_name():
print(f"the __name__ is '{__name__}'")
>>> import triangles
>>> triangles.read_name():
the __name__ is 'triangles'
We can reach two conclusions from this little experiment:
- Both
__name__
and__package__
variables are unique to each python file. - Its value is determined when the module is imported.
__name__
corresponds to the relative path of the file.__package__
corresponds to the relative path of the directory where the file is stored.- The backslashes are replaced by dots. For example, a file located in the directory
a/b/c
has the__package__
variable equal to ‘a.b.c
‘.
The special case for __main__
Python does not define an explicit entry-point on a module. For example, in languages such as C++, Rust or Go, we must define the main function which is when the application is run. Instead, Python runs the whole file and defines the __name__
variable especially as __main__
for the file which is executed. This is such that developers customize the file to run differently whether the file is loaded or executed.
To illustrate this behavior, let’s create a file app.py which output depending on the __name__
variable.
if __name__ == '__main__':
print("I'm being executed!")
else:
print("I'm being imported!")
$ python app.py
I'm being executed!
$ python -c "import app"
I'm being imported!
Why is __name__ so important in Python?
While this looks pretty interesting, why should you care about the value of __name__
? It depends on the kind of project you are developing. Its main goal is for developers to have executable code that can be imported, just like any library. The challenge here is to detect whether the library is being imported, or not, to determine if the executable portion is to be run. This is where the value of __name__ comes in.
From the user side, the experience is straightforward. Let’s look at the http.server
package, which has this behavior:
- For any user who wants to serve static files in a directory, it seems quite an effort to write a script to serve those files. So, the
http.server
library can be executed with the commandpython -m http.server
. This is not only straightforward but very useful for website developers that don’t even have to write python code. - For application or server developers, the
http.server
is a very useful library to create an HTTP endpoint. The library exposes several classes, such as theHTTPServer
, which can be included in any Python project which requires networking capabilities.
To understand how can we create such packages ourselves, let’s write a Python package to benchmark functions. That is, measure the time it takes to execute a function, given a number of parameters.
Let’s begin to create a new package called benchmarks with the Python poetry tool:
$ poetry new --name benchmarks benchmarks_py
Created package benchmarks in benchmarks_py
If you are not familiar with poetry, it’s a tool to develop python packages in a very simple way. Find more at the official website.
We will create a new file benchmark
.py in the directory benchmarks inside the package, with the following content.
import time
def benchmark(fn, num_calls, *args):
"""measure the execution time of any given function in seconds"""
tick = time.perf_counter(). #start counter
for _ in range(num_calls):
fn(*args) #Call the give function "num_call" times and get the average
tack = time.perf_counter()
return (tack - tick) / num_calls
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--num-calls', type=int, default=1000)
parser.add_argument('--module', type=str, required=True)
parser.add_argument('--function', type=str, required=True)
parser.add_argument('--args', nargs='*', type=int, default=[])
args = parser.parse_args(). #Check arguments
module = __import__(args.module).
fn = getattr(module, args.function). #dynamically load function
fn_args = args.args
num_calls = args.num_calls
bench = benchmark(fn, num_calls, *fn_args). #get average runtime
print("benchmark = ", bench)
This module exports a benchmark function, and if executed, it will load a function given by the cli arguments –module and –function, and execute it the number of calls given by –num-calls (1000 times by default).
You can see how the argparse module works in our other article on argparse
We can now build and install the package in our system with the command:
$ poetry install
$ pip install dist/benchmarks-0.1.0-py3-none-any.whl
Now, our library can be used everywhere on our system to benchmark Python functions.
As an example, let’s evaluate the performance of a simple function count_integers that sums the numbers from a to b:
def count_integers(a, b):
sum = 0
while a <= b:
sum += a
a += 1
return sum
Using the benchmark module as a library is as simple as importing the benchmark function. Note that, because the python module is imported, the code under the if is not executed:
>>> from benchmarks.benchmark import benchmark
>>> from count_integers import count_integers
>>> benchmark(count_integers, 1000, 0, 10000)
0.0007695084930019221
We can also run the python module benchmarks.benchmark
and define the arguments to run the count_integers function as well. This time, the if block is executed.
$ python -m benchmarks.benchmark \
--module count_integers \
--function count_integers \
--args 0 10000
\
--num-calls 1000
benchmark = 0.0007418607939907815
Conclusion
I hope all of you understand a bit more about the python module system, and why is __name__
used.
Get Notified Automatically Of New Articles
Error SendFox Connection: