Python sys.exit: a suggestion

I was surprised this morning when I realized that the call to sys.exit is not really exiting. It is instead raising exception SystemExit which, if left unhandled, will exit with the proper exit code.

First, a code sample.

import sys

sys.exit(1)

Running it and printing the exit code.

%> python test.py
%> echo $? # echo the exit code
1

As expected, the exit code is 1.

Here is a code sample to illustrate the SystemExit exception.

import sys

try:
    sys.exit(1)
except:
    print "Caught exception"

print "Still alive"

And the run output + exit code

%> python test.py
Caught exception
Still alive
%> echo $?
0

From the code sample, the result is pretty obvious, but imagine how in a larger program that could be surprising if you are unaware of the feature.

import sys
import logging

def do_stuff():
    # complex logic, eventually reach a point where we can simply
    # exit normally
    print "do_stuff exit"
    sys.exit(0)

if __name__ == '__main__':
    log = logging.getLogger(__name__)
    log.addHandler(logging.StreamHandler())
    log.setLevel(logging.DEBUG)
    try:
        do_stuff()
    except:
        log.exception("Caught exception at top level, exiting")
        raise
    log.info("Normal exit - never reached")

Output

%> python test.py
do_stuff exit
Caught exception at top level, exiting
Traceback (most recent call last):
  File "test.py", line 15, in <module>
    do_stuff()
  File "test.py", line 8, in do_stuff
    sys.exit(0)
SystemExit: 0

(The do_stuff function could simply return and the current situation would be avoided, but that’s simply to illustrate the point.) The exit code is correct, but the exception printed right before exiting is misleading. A simple additional exception handler can be added to counter it.

import sys
import logging

def do_stuff():
    # complex logic, eventually reach a point where we can simply
    # exit normally
    print "do_stuff exit"
    sys.exit(0)

if __name__ == '__main__':
    log = logging.getLogger(__name__)
    log.addHandler(logging.StreamHandler())
    log.setLevel(logging.DEBUG)
    try:
        do_stuff()
    except SystemExit:
        #move along
        pass
    except:
        log.exception("Caught exception at top level, exiting")
        raise
    log.info("Normal exit")

Output

%> python test.py
do_stuff exit
Normal exit

Ultimately, what might be the best solution is not to use “except” alone but “except Exception” such as

import sys
import logging

def do_stuff():
    # complex logic, eventually reach a point where we can simply exit normally
    print "do_stuff exit"
    sys.exit(0)

if __name__ == '__main__':
    log = logging.getLogger(__name__)
    log.addHandler(logging.StreamHandler())
    log.setLevel(logging.DEBUG)
    try:
        do_stuff()
    except Exception:
        log.exception("Caught exception at top level, exiting")
        raise
    log.info("Normal exit - never reached")

Output

%> python test.py
do_stuff exit

Leave a Reply