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