Exit codes and sys.exit

sys · Essentials · 7 min

Shells, CI pipelines and schedulers decide success or failure purely from a process's exit code.

A process reports success or failure to whoever started it with an exit code: 0 means success, anything from 1 to 255 means failure. Shell &&, set -e, CI jobs and cron alerts all depend on it.

sys.exit(arg) ends the program:

  • sys.exit() or sys.exit(0): success.
  • sys.exit(2): exit with code 2 (by convention, 2 means a usage error).
  • sys.exit("error: config missing"): prints the message to stderr and exits with code 1.

An uncaught exception also exits with code 1 after printing the traceback.

sys.exit does not stop the interpreter immediately. It raises the SystemExit exception. That means finally blocks and with statements still run their cleanup, and atexit handlers run afterwards. It also means a bare except: or except BaseException: will swallow it; except Exception: does not, because SystemExit derives directly from BaseException.

In a script, the common shape is:

if __name__ == "__main__":
    sys.exit(main())

where main() returns an int. Prefer sys.exit over the built-in exit()/quit(), which are meant for the interactive prompt. os._exit(code) exits instantly without any cleanup and is only for special cases such as forked children.

Example

import subprocess
import sys

def code_of(src):
    return subprocess.run([sys.executable, "-c", src],
                          capture_output=True, text=True)

print(code_of("import sys; sys.exit()").returncode)     # 0
print(code_of("import sys; sys.exit(3)").returncode)    # 3
r = code_of("import sys; sys.exit('bad config')")
print(r.returncode, r.stderr.strip())                   # 1 bad config
print(code_of("1 / 0").returncode)                      # 1

try:
    sys.exit(5)
except SystemExit as e:          # it is just an exception
    print("caught", e.code)      # caught 5
finally:
    print("cleanup runs")        # cleanup runs

Remember

  • Exit code 0 means success; non-zero means failure
  • sys.exit(n) raises SystemExit, so finally and with cleanup still run
  • sys.exit('msg') prints to stderr and exits with code 1

Watch out for

  • Wrapping code in except BaseException: or a bare except: that swallows SystemExit, so the program never exits.

Check yourself

  1. A function calls sys.exit(4) inside try: ... except Exception: print('oops'). What happens?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/sys.html#sys.exit