os._exit
sys.exit raises SystemExit and lets Python clean up; os._exit ends the process on the spot. Use it in a forked child, almost nowhere else. os._exit and os.EX_OK exist on Unix and Windows; the other EX_* codes are Unix only.
Common call
os._exit(0)
Returns
never returns
Replaces
sys.exit(n) is the normal way out
Watch out
unflushed print() output is lost
os._exit(n) / os.EX_OK, os.EX_USAGE, os.EX_DATAERR, ... (sysexits exit codes)
→ never returns
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| n | int | yes | The exit status. Unix keeps only the low 8 bits (0-255); 0 (EX_OK) means success. |
Return value
never returns — The process ends with exit status n. The EX_* names are plain int constants.
Common patterns
End a forked child (Unix)
Flush, then _exit, so the child never runs the parent code or its atexit handlers.
import os, sys pid = os.fork() if pid == 0: try: do_child_work() code = os.EX_OK except Exception: code = os.EX_SOFTWARE finally: sys.stdout.flush() os._exit(code)
Command-line tool with sysexits codes
EX_USAGE for bad arguments, EX_NOINPUT for a missing input file. getattr keeps it working on Windows.
import os, sys EX_USAGE = getattr(os, 'EX_USAGE', 64) EX_NOINPUT = getattr(os, 'EX_NOINPUT', 66) if len(sys.argv) != 2: print('usage: tool FILE', file=sys.stderr) sys.exit(EX_USAGE) if not os.path.exists(sys.argv[1]): sys.exit(EX_NOINPUT)
Hard stop from a watchdog thread
sys.exit in a thread only ends that thread; os._exit ends the whole process.
import os, sys, threading def watchdog(): if not finished.wait(timeout=60): print('timed out', file=sys.stderr, flush=True) os._exit(1) finished = threading.Event() threading.Thread(target=watchdog, daemon=True).start()
Examples
1. EX_OK is 0
import os
os.EX_OK
Returns
02. _exit sets the exit code
import subprocess, sys
r = subprocess.run([sys.executable, '-c', 'import os; os._exit(5)'])
r.returncode
Returns
53. finally blocks are skipped
import subprocess, sys
code = 'import os\ntry:\n os._exit(3)\nfinally:\n print("finally ran")'
r = subprocess.run([sys.executable, '-c', code], capture_output=True, text=True)
(r.returncode, r.stdout)
Returns
(3, '')4. sys.exit runs them
import subprocess, sys
code = 'import sys\ntry:\n sys.exit(3)\nfinally:\n print("finally ran")'
r = subprocess.run([sys.executable, '-c', code], capture_output=True, text=True)
(r.returncode, r.stdout)
Returns
(3, 'finally ran\n')5. sys.exit is just an exception
import sys
try:
sys.exit(3)
except SystemExit as e:
result = ('caught', e.code)
result
Returns
('caught', 3)6. sys.exit with a message: stderr and code 1
import subprocess, sys
r = subprocess.run([sys.executable, '-c', 'import sys; sys.exit("bye")'], capture_output=True, text=True)
(r.returncode, r.stderr)
Returns
(1, 'bye\n')7. EX_* with a fallback for Windows
import os
getattr(os, 'EX_USAGE', 64)
Returns
64Pitfalls
1. Expecting cleanup after os._exit
atexit handlers, finally blocks and object finalizers do not run. If cleanup matters, use sys.exit - or do the cleanup yourself before calling _exit.
os._exit
import subprocess, sys code = 'import atexit, os\natexit.register(print, "cleanup ran")\nos._exit(0)' subprocess.run([sys.executable, '-c', code], capture_output=True, text=True).stdout
''
sys.exit
import subprocess, sys code = 'import atexit, sys\natexit.register(print, "cleanup ran")\nsys.exit(0)' subprocess.run([sys.executable, '-c', code], capture_output=True, text=True).stdout
'cleanup ran\n'
2. Losing printed output
stdout to a pipe or file is block-buffered and _exit does not flush it. Flush (print(..., flush=True) or sys.stdout.flush()) before _exit.
no flush
import subprocess, sys code = 'import os; print("saved?", end=""); os._exit(0)' r = subprocess.run([sys.executable, '-c', code], capture_output=True, text=True) r.stdout
''
flush=True
import subprocess, sys code = 'import os; print("saved", end="", flush=True); os._exit(0)' r = subprocess.run([sys.executable, '-c', code], capture_output=True, text=True) r.stdout
'saved'
When to use
Use it
- The child process after os.fork() - the docs name this as the normal use
- Ending the whole process from a thread or after a fatal state where cleanup could hang
- EX_* codes: Unix command-line tools that want conventional, documented exit codes
Reach for something else
- Normal program exit → sys.exit(n) (or just return from main)
- Any exit where files, buffers or atexit handlers must be flushed
- EX_* names other than EX_OK in code that must run on Windows - they do not exist there
Notes
CPython impl
os._exit calls the C _exit() directly (Modules/posixmodule.c). sys.exit raises SystemExit, which unwinds the stack, then interpreter shutdown runs atexit handlers and flushes stdio.
Availability
_exit: everywhere. EX_OK: Unix, Windows. EX_USAGE ... EX_CONFIG: Unix only (not WASI), and only where the platform defines them.
Values on Linux
EX_OK 0, EX_USAGE 64, EX_DATAERR 65, EX_NOINPUT 66, EX_NOUSER 67, EX_NOHOST 68, EX_UNAVAILABLE 69, EX_SOFTWARE 70, EX_OSERR 71, EX_OSFILE 72, EX_CANTCREAT 73, EX_IOERR 74, EX_TEMPFAIL 75, EX_PROTOCOL 76, EX_NOPERM 77, EX_CONFIG 78.
Range
Unix keeps only the low 8 bits: in our test os._exit(256) gave exit code 0 and os._exit(-1) gave 255 on Linux, but 256 and 4294967295 on Windows. Stay within 0-255.
Meanings
EX_USAGE wrong usage/arguments, EX_DATAERR bad input data, EX_NOINPUT input file missing or unreadable, EX_NOUSER / EX_NOHOST unknown user / host, EX_UNAVAILABLE service unavailable, EX_SOFTWARE internal error, EX_OSERR OS error (e.g. cannot fork), EX_OSFILE system file problem, EX_CANTCREAT cannot create output file, EX_IOERR I/O error, EX_TEMPFAIL temporary failure (retry later), EX_PROTOCOL protocol error, EX_NOPERM insufficient permission, EX_CONFIG configuration error.
FAQ
os._exit would end the page process itself, so the examples call it only in a separate Python child. os._exit and os.EX_OK work on Unix and Windows; EX_USAGE, EX_DATAERR and the other EX_* codes are Unix only (not WASI), so on Windows os.EX_USAGE raises AttributeError - use getattr(os, "EX_USAGE", 64) for portable code.