SystemExit

sys.exit() is just raise SystemExit(code): finally blocks run, it can be caught, and the code decides the exit status — None → 0, int → itself, anything else → printed, status 1.

InheritsBaseException›SystemExit
Control-flow exceptionPython 3 (all)Live demo
SystemExit([code])
Raised by
sys.exit(code), exit()/quit() in the REPL, argparse on bad arguments (code 2)
Message
no traceback printed; a non-int code is printed to stderr
Quick fix
sys.exit(0) success, sys.exit(1) failure, sys.exit("msg") error + status 1
Watch out
a bare except: catches it and the program keeps running

Demo

Live evaluation
Call sys.exit() and catch the SystemExit it raises. e.code is exactly the argument you passed.
Try:
Inputs
codeint | float | strnumbers stay numbers
Code
import sys
try:
    sys.exit(3)
except SystemExit as e:
    r = e.code
r
Result
3

e.code is stored as-is — 3 stays an int, a message stays a string, and SystemExit() with no argument has code None. The interpreter converts it to a process status only at the very end: None → 0, int → that number, anything else is printed to stderr and becomes 1. The demo catches it on purpose; uncaught, the program would simply end without a traceback.

Constructor

NameTypeRequiredDescription
codeint | str | Noneno (None)The exit status or message, stored as e.code. None (or no argument) → status 0; an int → that status; any other object → printed to stderr, status 1. With several arguments, code is the whole args tuple.

Attributes

AttributeTypeMeaning
codeobjectThe exit status or message passed to the constructor / sys.exit(). None by default; the args tuple when there are several arguments.
argstupleConstructor arguments. sys.exit() with no argument gives an empty tuple.

Common patterns

Script entry point with an exit status
Return an int from main() and pass it to sys.exit — shells and CI read the status.
import sys

def main() -> int:
    if not check():
        print('check failed', file=sys.stderr)
        return 1
    return 0

if __name__ == '__main__':
    sys.exit(main())
Exit with an error message
A string code is printed to stderr and the status is 1 — the shortest correct way to fail.
if not os.path.exists(path):
    sys.exit(f'error: {path} not found')
Testing code that calls sys.exit
Catch the SystemExit and assert on the code (pytest.raises(SystemExit) does the same).
try:
    cli(['--bad-flag'])
except SystemExit as e:
    assert e.code == 2

Examples

1. sys.exit() with no argument
import sys try: sys.exit() except SystemExit as e: r = (e.code, e.args) r
Returns
(None, ())
2. Exit statuses the OS sees
import subprocess, sys def status(arg): cmd = [sys.executable, '-c', f'import sys; sys.exit({arg})'] return subprocess.run(cmd, capture_output=True).returncode [status(a) for a in ['', 'None', '0', '3', 'True', "'bye'"]]
Returns
[0, 0, 0, 3, 1, 1]
3. A string code goes to stderr
import subprocess, sys cmd = [sys.executable, '-c', "import sys; sys.exit('fatal: no config')"] r = subprocess.run(cmd, capture_output=True, text=True) (r.returncode, r.stderr)
Returns
(1, 'fatal: no config\n')
4. except Exception does not catch it
import sys try: try: sys.exit(4) except Exception: r = 'swallowed' except SystemExit as e: r = f'still exiting with {e.code}' r
Returns
'still exiting with 4'
5. Several arguments: code is the tuple
SystemExit(1, 2).code
Returns
(1, 2)
6. os._exit skips finally
import subprocess, sys src = "import os\ntry:\n os._exit(4)\nfinally:\n print('cleanup')" r = subprocess.run([sys.executable, '-c', src], capture_output=True, text=True) (r.returncode, r.stdout)
Returns
(4, '')
7. argparse exits with code 2
import argparse p = argparse.ArgumentParser(prog='tool') p.add_argument('--n', type=int) try: p.parse_args(['--n', 'x']) except SystemExit as e: r = e.code r
Returns
2

Pitfalls

1. Passing the status as a string
sys.exit('0') is a message, not a status: it prints 0 to stderr and exits with 1. Convert to int.
sys.exit('0')
import subprocess, sys
cmd = [sys.executable, '-c', "import sys; sys.exit('0')"]
r = subprocess.run(cmd, capture_output=True, text=True)
(r.returncode, r.stderr)
(1, '0\n')
sys.exit(0)
import subprocess, sys
cmd = [sys.executable, '-c', "import sys; sys.exit(0)"]
r = subprocess.run(cmd, capture_output=True, text=True)
(r.returncode, r.stderr)
(0, '')
2. argparse errors are not Exceptions
On bad arguments argparse prints usage and calls sys.exit(2), which sails past except Exception. Use exit_on_error=False to get argparse.ArgumentError instead.
except Exception
import argparse
p = argparse.ArgumentParser(prog='tool')
p.add_argument('--n', type=int)
try:
    try:
        p.parse_args(['--n', 'x'])
    except Exception:
        r = 'handled'
except SystemExit as e:
    r = f'exited with {e.code}'
r
'exited with 2'
exit_on_error=False
import argparse
p = argparse.ArgumentParser(prog='tool', exit_on_error=False)
p.add_argument('--n', type=int)
try:
    p.parse_args(['--n', 'x'])
except argparse.ArgumentError as e:
    r = str(e)
r
"argument --n: invalid int value: 'x'"
3. A bare except cancels the exit
except: catches SystemExit, so the "exit" becomes a no-op and the code after it runs.
bare except
import sys
log = []
try:
    sys.exit(1)
except:
    log.append('error ignored')
log.append('still running')
log
['error ignored', 'still running']
except Exception
import sys
log = []
try:
    try:
        sys.exit(1)
    except Exception:
        log.append('error ignored')
    log.append('still running')
except SystemExit:
    log.append('exited')
log
['exited']

When to use

Use it
  • Ending a script with a status code other programs can check
  • Failing fast with a message: sys.exit("error: ...")
  • Catching it in tests or in a host that runs user scripts
Reach for something else
  • Signalling an error inside a library → raise a real exception; let the caller decide to exit
  • Immediate exit that skips cleanup (after fork) → os._exit()
  • exit() / quit() in scripts → they are REPL helpers added by site; use sys.exit()

Notes

CPython impl
Python/pythonrun.c — handle_system_exit() turns e.code into the process status and prints non-int codes to stderr
No traceback
An uncaught SystemExit ends the interpreter without printing a traceback
Threads
sys.exit() in a non-main thread only ends that thread
Status range
Most systems expect 0–127; on POSIX the status is truncated to 8 bits, so sys.exit(256) looks like success

FAQ

sys.exit() and sys.exit(None) exit with 0. sys.exit(n) with an int exits with n. Any other value — sys.exit('message') — is printed to stderr and the status is 1. True counts as the int 1. A string that looks like a number is still a string: sys.exit('0') exits with 1.