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