The Python sys module provides access to variables and functions used by the Python interpreter itself — command-line arguments, standard streams, the module import system, recursion limits, and interpreter version information.
Accessing the Python interpreter from your code
What you'll learn: sys.argv for command-line arguments, sys.exit() for clean program termination, and sys.path (the module search path).
How to read this tab: Create a script that prints sys.argv, then run it with: python script.py hello world 42. See what sys.argv contains.
The sys module is your window into the Python interpreter itself — it lets you read command-line arguments, control stdin/stdout/stderr, inspect the import system, and exit the program.
sys.argv is always a list of strings. Even if you pass 42 as an argument, sys.argv contains "42" (a string). Convert explicitly: int(sys.argv[1]). For production CLIs with many arguments, use the argparse module instead of parsing sys.argv manually.
Command-line arguments
import sys
# sys.argv[0] is the script name
# sys.argv[1:] are the arguments passed
# python3 script.py hello world --verbose
print(sys.argv) # ['script.py', 'hello', 'world', '--verbose']
print(sys.argv[0]) # script.py
print(sys.argv[1:]) # ['hello', 'world', '--verbose']
# Simple argument parsing
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <name>", file=sys.stderr)
sys.exit(1) # exit with error code
name = sys.argv[1]
print(f"Hello, {name}!")
# For complex argument parsing, use argparse (stdlib) insteadPrint to stderr for error messages. print("Error: something went wrong", file=sys.stderr). This keeps error output separate from normal output — important for scripts used in pipelines where stdout is redirected to a file.
stdin, stdout, stderr
import sys
# Write to stdout (same as print but explicit)
sys.stdout.write("Hello\n")
# Write to stderr — for error messages and diagnostics
sys.stderr.write("Error: something went wrong\n")
print("Error:", exc_info, file=sys.stderr) # print to stderr
# Read from stdin
line = sys.stdin.readline()
for line in sys.stdin: # iterate all lines
process(line.strip())
# Redirect stdout
import io
buffer = io.StringIO()
sys.stdout = buffer
print("captured")
sys.stdout = sys.__stdout__ # restore
output = buffer.getvalue() # "captured\n"Useful for cross-version compatibility. sys.version gives the full version string. sys.version_info gives a named tuple (major, minor, micro). Check: if sys.version_info < (3, 9): raise RuntimeError("Python 3.9+ required").
Python interpreter information
import sys
print(sys.version) # '3.13.0 (main, Oct 2024, ...)' — full version string
print(sys.version_info) # sys.version_info(major=3, minor=13, micro=0, ...)
print(sys.platform) # 'linux', 'darwin', 'win32'
print(sys.executable) # '/usr/bin/python3' — path to interpreter
print(sys.prefix) # installation directory
# sys.path — where Python looks for modules to import
print(sys.path)
sys.path.insert(0, "/my/custom/modules") # add custom path
# Check Python version
if sys.version_info < (3, 10):
raise RuntimeError("Python 3.10+ required")✅ Beginner tab complete — check your understanding
- I know sys.argv[0] is the script name; sys.argv[1:] are the arguments
- I can use sys.exit(0) for success and sys.exit(1) for failure
- I know sys.path is the list of directories Python searches for modules
stdin, stdout, stderr, and sys.modules
What you'll learn: The three standard streams (stdin, stdout, stderr) and how to redirect them. sys.modules as the module cache. Recursion limits.
How to read this tab: Try redirecting stdout to a file: sys.stdout = open("output.txt", "w") then print() goes to the file. Remember to restore it: sys.stdout = sys.__stdout__.
sys.modules is the import cache. When you import a module, Python checks sys.modules first. If it's there, it reuses the cached module object — doesn't re-execute the file. You can check if something is imported: "json" in sys.modules. You can even force a reimport by deleting from sys.modules (rare but sometimes useful in tests).
The import system and sys.modules
sys.modules is a dictionary mapping module names to already-imported module objects. When you import foo, Python checks sys.modules first — if it's there, it returns the cached module. This is why importing the same module twice is cheap. You can inspect it, add mock modules, or even remove a module to force reimport.
import sys
import json
# Already imported
print("json" in sys.modules) # True
print(sys.modules["json"] is json) # True — same object
# Force reimport (rarely needed)
del sys.modules["json"]
import json # fresh import
# Mock a module for testing
import types
mock_requests = types.ModuleType("requests")
mock_requests.get = lambda url: type("Response", (), {"status_code": 200})()
sys.modules["requests"] = mock_requests
import requests # returns our mockHitting the recursion limit gives you RecursionError. The default is 1000 levels deep. If your recursive algorithm legitimately needs more (large trees, deep graphs), increase it with sys.setrecursionlimit(5000). But first ask: can I rewrite this iteratively? Deep recursion in Python is slower than iteration and risks stack overflow.
sys.getrecursionlimit and sys.setrecursionlimit
import sys
# Default is 1000
print(sys.getrecursionlimit()) # 1000
# Increase if needed (with care)
sys.setrecursionlimit(10000)
# Check recursion depth
def depth_check():
frame = sys._getframe()
d = 0
while frame:
frame = frame.f_back
d += 1
return d
# sys.getsizeof — memory usage of an object
import sys
print(sys.getsizeof([])) # 56 bytes (empty list)
print(sys.getsizeof([1,2,3])) # 88 bytes (3 ints + list overhead)
print(sys.getsizeof("hello")) # 54 bytes
# Note: getsizeof does NOT include objects referenced by the objectsys.exit() raises SystemExit, it does not immediately terminate. sys.exit() raises a SystemExit exception. This means finally blocks and atexit handlers still run. If caught by a bare except:, the program will not exit. Use os._exit() only if you need immediate termination without cleanup.sys.getsizeof() does not measure total memory of nested objects. It returns only the memory used by the object itself, not by any objects it references. A list of 1000 strings: sys.getsizeof(lst) gives the list's own memory (pointers). The strings' memory is not included. Use tracemalloc for full memory profiling.sys.path manipulation is permanent for the current process. Modifying sys.path affects all subsequent imports in the current Python session. For repeatable setups, use virtual environments and PYTHONPATH instead.✅ Intermediate tab complete — check your understanding
- I know the difference between stdout (normal output) and stderr (error output)
- I can check if a module is already imported with "name" in sys.modules
- I know sys.getrecursionlimit() and can increase it with sys.setrecursionlimit()
CPython frame objects and audit hooks
What you'll learn: sys._getframe() for inspecting the call stack. sys.audit hooks (Python 3.8+) for security monitoring. CPython implementation details.
How to read this tab: Read when building debugging tools, security auditing systems, or needing to inspect the call stack dynamically.
Frame objects represent function calls. Each function call creates a frame on the call stack. sys._getframe() gives you the current frame, frame.f_back gives the caller, and so on up the stack. This is how debuggers, profilers, and logging libraries like logging know where they were called from.
CPython frame objects and the frame stack
sys._getframe(depth) returns the call frame at the given depth. A frame object contains: f_code (the code object), f_locals (local variables dict), f_globals (global variables dict), f_back (the calling frame), f_lineno (current line). The leading underscore signals this is a CPython implementation detail — not guaranteed to exist in other Python implementations (PyPy, Jython). This is the mechanism behind tracebacks, debuggers (pdb), and profilers (cProfile).
sys.audit hooks (Python 3.8+)
PEP 578 added sys.audit(event, *args) and sys.addaudithook(hook) — a security mechanism for monitoring or restricting potentially dangerous operations. When sys.audit("open", file, mode) is called internally by CPython (on file open, exec, import, etc.), all registered audit hooks are called. This enables security frameworks to monitor or veto operations. Audit hooks cannot be removed once added — only new hooks can be added. This is used by the CPython restricted execution proposals.
✅ Expert tab complete
- I know sys._getframe() returns the current execution frame object
- I understand what sys.addaudithook() does and when it's used