Blog

Links

Dual-purpose Python scripts and modules

posted: May 6, 2023

tl;dr: How I structure a .py file so that it works as a script and an importable module...

Just what is a Python module? The answer is, basically, a .py file, as one .py file can be imported into another .py file. But there is a trick to writing your .py files that you probably want to take advantage of to make an easy-to-use module.

There's a fact-of-life with the Python interpreter that you need to deal with: when the Python interpreter imports a module, it immediately runs all the code in that module. This is fine-and-dandy and is what you want when you are defining stuff, such as functions, constants, and classes for use later. But if your .py file also contains Python statements that produce side effects and which you only want to execute when you are running your .py file as a script, such as print('Hello, world!'), how do you prevent the interpreter from executing those statements when your .py file is imported as a module?

The answer is to create a code block at the bottom of your .py file that starts with if __name__ == '__main__': and then put your script code in that block (indented by four spaces, of course). That script code can call other functions defined in the .py file such as main(), which is a very common pattern, as you can then write your top-level script code in the main() function. In effect, the if __name__ == '__main__': statement creates the entry point for your .py file when it is executed as a script.

But what is __name__? Any time you see double underscores in Python, it is something that the interpreter treats as "special" or "magic". By the way "double underscore" is shortened to "dunder" when Pythonistas talk, so we would read that statement as "if dunder name is equal to the string dunder main". Dunder name is an internal interpreter variable that the interpreter sets to the value '__main__' when your .py file is executed as a script, i.e. by typing python3 my_script.py on the command line. When your .py file is imported, dunder name won't be equal to '__main__', and any code in that block will not be executed - but all the other top-level Python statements, such as your def defines, will be executed, which is what you want when importing your .py file as a module. Problem solved!

You can see what the value of __name__ is when you invoke the Python interpreter. Other modules that you import will have their own __name__ values, as do other objects in Python including the print() function.

Ten lines of output from a computer terminal, consisting of white characters on a black background

The __name__ attribute is but one example of Python's dunder magic

By the way, violating the if __name__ == '__main__': trick is how two “Easter egg” Python modules work, the this module and the antigravity module. Those modules don't define anything useful, and they are entirely side effects (type import this and import antigravity in the Python interpreter to see). Those modules just consist of Python statements that create the side effects, and those statements are not protected by a if __name__ == '__main__': block, so when you import them (refer back to the bold statement above) the Python interpreter executes them and the side effects are produced.

With this as background, here is my standard template for creating a new Python script that also works as an importable module:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Instructions and tips on how to run the script go here

# Standard library imports

# 3rd party library imports

# Constants


def main():
    pass


if __name__ == '__main__':
    main()

Key points:

Further reading:

Python File Importation into Multi-Level Directory Modules and Packages