Exiting a Python program PREMIUM

Series: Modules
Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
6 min. read Watch as video Python 3.10—3.14
Python Morsels
Watch as video
05:45

What if you wanted to exit a Python program early, or indicate an error while exiting?

A program that validates files

Here we have a program called fixme.py:

from argparse import ArgumentParser
from pathlib import Path

parser = ArgumentParser()
parser.add_argument("path", type=Path, nargs="?", default=".")
args = parser.parse_args()

fixmes = 0
for path in args.path.rglob("*.py"):
    if "FIXME" in path.read_text():
        print(f"{path} contains FIXME(s)")
        fixmes += 1
print(f"{fixmes} files with FIXME")

This program accepts a directory name and validates that none of the .py files in that directory contain the string FIXME.

Let's run this against a random_names directory:

$ python3 fixme.py random_names
random_names/src/main.py contains FIXME(s)
random_names/src/names.py contains FIXME(s)
2 files with FIXME

Our fixme.py found two files in that directory which contain the string FIXME.

Exiting early with sys.exit

Right now, if we run this program with a directory that doesn't exist, it prints out 0 files with FIXME:

$ python3 fixme.py /home/unicorn
0 files with FIXME

That doesn't really make sense. If the given doesn't exist, our program should probably print out an error instead.

Let's modify our program to make sure that the given directory exists. If the directory doesn't exist, we'll print out an error message and then exit our program.

from argparse import ArgumentParser
from pathlib import Path
import sys

parser = ArgumentParser()
parser.add_argument("path", type=Path, nargs="?", default=".")
args = parser.parse_args()

if not args.path.is_dir():
    print(f"{args.path} is not a directory")
    sys.exit()

fixmes = 0
for path in args.path.rglob("*.py"):
    if "FIXME" in path.read_text():
        print(f"{path} contains FIXME(s)")
        fixmes += 1
print(f"{fixmes} files with FIXME")

We're using the sys.exit function to exit our program.

When we run this program again, we'll see an error message prints out and then our program exits:

$ python3 fixme.py /home/unicorn
/home/unicorn is not a directory

But the way we're exiting here isn't really ideal.

Using an exit code to indicate an error

System command prompts on pretty much all operating systems support a double ampersand (&&) operator that can run another command if the first command was successful.

Let's use our system's built-in echo command to print Success if (and only if) our fixme.py program exited successfully:

$ python3 fixme.py /home/unicorn && echo "Success"

Right now, our system command prompt thinks our fixme.py program exited successfully:

$ python3 fixme.py /home/unicorn && echo "Success"
/home/unicorn is not a directory
Success

But it really shouldn't have, we purposely printed out an error message and exited early.

The sys.exit function accepts an optional exit code and the default exit code is 0:

if not args.path.is_dir():
    print(f"{args.path} is not a directory")
    sys.exit(0)

All command prompts agree that exit code 0 means success.

A non-zero exit code means failure. So let's exit our program with exit code 1 instead:

if not args.path.is_dir():
    print(f"{args.path} is not a directory")
    sys.exit(1)

As expected, our command prompt doesn't print Success this time:

$ python3 fixme.py /home/unicorn && echo "Success"
/home/unicorn is not a directory

Whenever you're trying to exit your program while indicating that an error occurred, call sys.exit with a number besides 0.

Printing an error message while exiting

There's actually a shortcut for printing an error message and exiting at the same time.

We can call sys.exit with a string:

if not args.path.is_dir():
    sys.exit(f"{args.path} is not a directory")

When sys.exit is called with a string, Python prints that string and exits with the code 1.

$ python3 fixme.py /home/unicorn && echo "Success"
/home/unicorn is not a directory

It's so common to print an error while exiting with an error code that this functionality was built-in to the sys.exit function.

Passing a boolean to sys.exit

You'll sometimes see Python programmers pass a boolean to sys.exit.

Here we're converting our fixmes count to a boolean and passing that boolean to sys.exit.

from argparse import ArgumentParser
from pathlib import Path
import sys

parser = ArgumentParser()
parser.add_argument("path", type=Path, nargs="?", default=".")
args = parser.parse_args()

if not args.path.is_dir():
    sys.exit(f"{args.path} is not a directory")

fixmes = 0
for path in args.path.rglob("*.py"):
    if "FIXME" in path.read_text():
        print(f"{path} contains FIXME(s)")
        fixmes += 1
print(f"{fixmes} files with FIXME")
sys.exit(bool(fixmes))

Note that last sys.exit(bool(fixmes)) line.

In Python, when you give the bool function a number, it returns False if that number is 0 and True otherwise (this is called truthiness):

>>> bool(0)
False
>>> bool(5)
True

For historical reasons, False in Python is equal to the number 0 and True is equal to the number 1:

>>> False == 0
True
>>> True == 1
True

That might seem strange, but Python sees booleans as a subset of integers.

So passing bool(fixmes) to sys.exit means "call sys.exit with 0 if we had no fixmes or 1 if we did have fixmes".

We'll use || in our system command prompt to print Error if (and only if) our fixme.py program exited with an error code:

$ python3 fixme.py random_names || echo "Error"

Before we added that final sys.exit(bool(fixmes)) line, we wouldn't have printed Error:

$ python3 fixme.py random_names
random_names/src/main.py contains FIXME(s)
random_names/src/names.py contains FIXME(s)
2 files with FIXME

But with that final line, we do see Error printed out:

$ python3 fixme.py random_names || echo "Error"
2 files with FIXME
Error

Stick with simple exit codes

Why can't we take bool out entirely?

sys.exit(fixmes)

We already have an integer (fixmes) and sys.exit accepts integers. A 0 exit code indicates success and anything else indicates an error, right?

The problem is that some operating systems have undefined behavior for exit codes above 127. Only the exit code 0 to 127 are well-defined (the Python documentation for sys.exit notes this).

With our bool conversion, we're always converting our fixmes integer to either 0 (False really) or 1 (True):

sys.exit(bool(fixmes))

Don't use the built-in exit function

You may sometimes see Python programmers call exit instead of sys.exit:

from argparse import ArgumentParser
from pathlib import Path

parser = ArgumentParser()
parser.add_argument("path", type=Path, nargs="?", default=".")
args = parser.parse_args()

if not args.path.is_dir():
    exit(f"{args.path} is not a directory")

fixmes = 0
for path in args.path.rglob("*.py"):
    if "FIXME" in path.read_text():
        print(f"{path} contains FIXME(s)")
        fixmes += 1
print(f"{fixmes} files with FIXME")
exit(bool(fixmes))

This is actually a potential bug in our code. It does work, but it might not always work.

If you pass a -S flag to Python, the site module won't be preloaded so the exit function won't exist:

$ python3 -S fixme.py random_names || echo "Error"
NameError: name 'exit' is not defined
Error

This exit function is intended to be used from the Python REPL only. The sys.exit is meant for exiting a Python program.

The SystemExit exception also exits

There is one more way to exit, though!

Instead of calling sys.exit, we could raise a SystemExit exception:

from argparse import ArgumentParser
from pathlib import Path

parser = ArgumentParser()
parser.add_argument("path", type=Path, nargs="?", default=".")
args = parser.parse_args()

if not args.path.is_dir():
    raise SystemExit(f"{args.path} is not a directory")

fixmes = 0
for path in args.path.rglob("*.py"):
    if "FIXME" in path.read_text():
        print(f"{path} contains FIXME(s)")
        fixmes += 1
print(f"{fixmes} files with FIXME")
raise SystemExit(bool(fixmes))

Raising a SystemExit exception does the exact same thing as calling sys.exit (both accept either a string or an integer).

Raising a SystemExit exception is slightly less common than calling the sys.exit function. But if you prefer this style, you're welcome to use it.

Summary

If you want to exit a Python program early, call the sys.exit function or raise a SystemExit exception.

If you're trying to indicate an error code while exiting, pass a number from 0 to 127 to sys.exit, where 0 indicates success and anything else indicates an error.

Or you could pass a string to sys.exit or SystemExit. Python will print out that string and exit with the error code 1.

Series: Modules

Modules are the tool we use for breaking up our code into multiple files in Python. When you write a .py file, you're making a Python module. You can import your own modules, modules included in the Python standard library, or modules in third-party packages.

To track your progress on this Python Morsels topic trail, sign in or sign up.

0%
Python Morsels
Watch as video
05:45
This is a free preview of a premium screencast. You have 2 previews remaining.