Monday, October 26, 2020

Stack Abuse: What Does if __name__ == "__main__": Do in Python?

Introduction

It's common to see if __name__ == "__main__" in Python scripts we find online, or one of the many we write ourselves.

Why do we use that if-statement when running our Python programs? In this article, we explain the mechanics behind its usage, the advantages, and where it can be used.

The __name__ Attribute and the __main__ Scope

The __name__ attribute comes by default as one of the names in the current local scope. The Python interpreter automatically adds this value when we are running a Python script or importing our code as a module.

Try out the following command on your Python interpreter. You may find out that __name__ belongs to the list of attributes in dir():

dir()

dir() output

The __name__ in Python is a special variable that defines the name of the class or the current module or the script from which it gets invoked.

Create a new folder called name_scripts so we can write a few scripts to understand how this all works. In that folder create a new file, script1.py with the following code:

print(f'The __name__ from script1 is "{__name__}"')

script1.py output

That's a curveball! We'd expect that the name would be script1, as our file. What does the output __main__ mean?

By default, when a script is executed, the interpreter reads the script and assigns the string __main__ to the __name__ keyword.

It gets even more interesting when the above script gets imported to another script. Consider a Python file named script2.py with the following code:

import script1  # The print statement gets executed upon import

print(f'The __name__ from script2 is "{__name__}"')

script2.py output

As you can see, when the script is executed the output is given as script1 denoting the name of the script. The final print statement is in the scope of script2 and when it gets executed, the output gets printed as: __main__.

Now that we understand how Python uses the __name__ scope and when it gives it a value of "__main__", let's look at why we check for its value before executing code.

if __name__ == "__main__" in Action

We use the if-statement to run blocks of code only if our program is the main program executed. This allows our program to be executable by itself, but friendly to other Python modules who may want to import some functionality without having to run the code.

Consider the following Python programs:

a) script3.py contains a function called add() which gets invoked only from the main context.

def add(a, b):
    return a+b


if __name__ == "__main__":
    print(add(2, 3))

Here's the output when script3.py gets invoked:

script3.py output

As the script was executed directly, the __name__ keyword is assigned to __main__, and the block of code under the if __name__ == "__main__" condition is executed.

b) Here's what happens when this snippet is imported from script4.py:

import script3

print(f"{script3.__name__}")

script4.py output

The block under if __name__ == "__main__" from script3.py did not execute, as expected. This happened because the __name__ keyword is now assigned with the name of the script: script3. This can be verified by the print statement given which prints the assigned value for the __name__ keyword.

How Does __name__ == "__main__" Help in Development?

Here are some use cases for using that if-statement when creating your script

  • Testing is a good practice which helps not only catch bugs but ensure your code behaves as required. Test files have to import a function or object to them. In these cases, we typically don't want the script being run as the main module.
  • You're creating a library but would like to include a demo or other special run-time cases for users. By using this if-statement, the Python modules that use your code as a library are unaffected.

Creating a __main__.py File for Modules

The point of having the if __name__ == "__main__" block is to get the piece of code under the condition to get executed when the script is in the __main__ scope. While creating packages in Python, however, it's better if the code to be executed under the __main__ context is written in a separate file.

Let's consider the following example - a package for performing calculations. The file tree structure for such a scenario can be visualized as:

calc                 # --> Root directory
├── __main__.py
├── script1.py
├── script2.py
├── script3.py
├── script4.py
└── src              # --> Sub-directory
    ├── add.py
    └── sub.py

The tree structure contains calc as the root directory and a sub-directory known as src. The __main__.py under the calc directory contains the following content:

from src.add import add
from src.sub import sub

a, b = input("Enter two numbers separated by commas: ").split(',')
a, b = int(a), int(b)

print(f"The sum is: {add(a, b)}")
print(f"The difference is: {sub(a, b)}")

The add.py contains:

def add(a, b):
    return a+b

And sub.py contains:

def sub(a, b):
    return a-b

From right outside the calc directory, the script can be executed and the logic inside the __main__.py gets executed by invoking:

python3 calc

script1.py output

This structure also gives a cleaner look to the workspace location, the way how the directories are organized, and the entry point is defined inside a separate file called __main__.py.

Conclusion

The __name__ == "__main__" runs blocks of code only when our Python script is being executed directly from a user. This is powerful as it allows our code to have different behavior when it's being executed as a program instead of being imported as a module.

When writing large modules, we can opt for the more structured approach of having a __main__.py file to run a module. For a stand-alone script, including the if __name__ == "__main__" is a simpler method to separate the API from the program.



from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...