Python sys Module
In this tutorial, we'll use the sys module to read command-line arguments, inspect Python, exit a program, and work with its input and output.
Python's sys module exposes information and controls related to the running Python interpreter. It is commonly used by command-line programs, diagnostics, import tooling, and programs that need to exit with a meaningful status.
It is available in the standard library:
import sys
Command-line arguments with sys.argv
sys.argv is a list containing the command used to start the script and any arguments supplied after it.
Create greet.py:
import sys
print(sys.argv)
Run it from a terminal:
python greet.py Ada 3
The list will look similar to:
['greet.py', 'Ada', '3']
sys.argv[0]is the script name or invocation path.- Remaining items are argument strings.
- Numeric arguments require explicit conversion.
A small program can validate its arguments:
import sys
def main():
if len(sys.argv) != 3:
print("Usage: python greet.py NAME COUNT", file=sys.stderr)
return 2
name = sys.argv[1]
try:
count = int(sys.argv[2])
except ValueError:
print("COUNT must be an integer", file=sys.stderr)
return 2
for _ in range(count):
print(f"Hello, {name}!")
return 0
if __name__ == "__main__":
raise SystemExit(main())
For commands with options, subcommands, and automatically generated help, use the standard-library argparse module instead of manually processing a large sys.argv list.
Exiting a program
sys.exit() requests that Python stop by raising SystemExit:
import sys
if configuration_is_invalid:
print("Invalid configuration", file=sys.stderr)
sys.exit(1)
By convention:
0means success.- A non-zero status means an error or another unsuccessful outcome.
Passing a string displays it on standard error and uses a non-zero status:
sys.exit("A required setting is missing")
Because SystemExit is an exception, finally blocks and context-manager cleanup can still run. In library code, raise an appropriate exception rather than terminating the entire application.
Standard input, output, and error
Python provides three standard streams:
| Stream | Purpose | | --- | --- | | sys.stdin | Program input | | sys.stdout | Normal output | | sys.stderr | Warnings and errors |
input() normally reads from sys.stdin, and print() normally writes to sys.stdout.
Send errors to the appropriate stream:
import sys
print("Report created")
print("Warning: one row was skipped", file=sys.stderr)
This separation matters when a caller redirects normal output to a file:
python report.py > report.txt
Warnings remain visible rather than being mixed into report.txt.
Process input line by line without loading it all at once:
import sys
for line in sys.stdin:
cleaned = line.strip()
if cleaned:
print(cleaned.upper())
This program can receive piped input:
python uppercase.py < names.txt
Python version information
sys.version_info is a tuple-like object designed for comparisons:
import sys
print(sys.version_info.major)
print(sys.version_info.minor)
if sys.version_info < (3, 11):
raise RuntimeError("Python 3.11 or newer is required")
sys.version is a human-readable string containing more build information, but parsing that string is fragile. Use version_info for program logic.
Finding the active Python interpreter
sys.executable contains the path of the interpreter running the program:
import sys
print(sys.executable)
This is useful when several Python installations or virtual environments exist. When launching another Python process, using sys.executable helps select the same interpreter:
import subprocess
import sys
subprocess.run([sys.executable, "worker.py"], check=True)
Detecting the platform
sys.platform identifies the platform on which Python is running:
import sys
if sys.platform == "win32":
print("Running on Windows")
elif sys.platform == "darwin":
print("Running on macOS")
elif sys.platform.startswith("linux"):
print("Running on Linux")
Prefer cross-platform APIs whenever possible. Platform-specific branches increase the number of behaviours you must test.
Understanding sys.path
sys.path is the list of locations Python searches when importing modules:
import sys
for location in sys.path:
print(location)
It can include the script directory, standard-library directories, installed package locations, and paths configured through the environment.
Although sys.path.append() can make an import work temporarily, modifying the import path inside application code is often a sign that the project should be packaged or launched differently.
Prefer:
- a clear package structure
- running modules with
python -m package.module - installing your project in a virtual environment
- configuring test tools correctly
Avoid relying on machine-specific absolute paths.
Loaded modules
sys.modules maps module names to modules already loaded in the current process:
import sys
print("json" in sys.modules)
import json
print("json" in sys.modules)
Python uses this cache to avoid executing the same module from scratch on every import. Editing sys.modules is an advanced technique and can easily create confusing import behaviour.
Object sizes
sys.getsizeof() reports the shallow memory size of an object in bytes:
import sys
numbers = [1, 2, 3]
print(sys.getsizeof(numbers))
The result includes the list object but not necessarily all objects referenced by the list. It also varies between Python implementations and builds. Do not treat it as a complete memory profiler.
Recursion limits
Python limits recursion depth to protect the interpreter stack:
import sys
print(sys.getrecursionlimit())
sys.setrecursionlimit() can change it, but increasing the limit may crash the process if recursion consumes the native stack. Rework unexpectedly deep recursion into an iterative solution instead of raising the limit blindly.
A practical command-line program
import sys
def parse_numbers(arguments):
try:
return [float(value) for value in arguments]
except ValueError as error:
raise ValueError("every argument must be a number") from error
def main(arguments):
if not arguments:
print("Usage: python average.py NUMBER ...", file=sys.stderr)
return 2
try:
numbers = parse_numbers(arguments)
except ValueError as error:
print(error, file=sys.stderr)
return 2
print(sum(numbers) / len(numbers))
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
Passing arguments into main() keeps the core behaviour easy to test without modifying global interpreter state.
Common sys mistakes
- Forgetting that every command-line argument starts as a string.
- Reading
sys.argv[1]without checking its length. - Returning success status
0after an error. - Calling
sys.exit()inside reusable library code. - Mixing diagnostics into standard output.
- Parsing the human-readable
sys.versionstring. - Adding hard-coded machine paths to
sys.path. - Assuming
getsizeof()measures an entire object graph. - Increasing the recursion limit without understanding the risk.
Practice
1. Write a script that accepts a name and prints a greeting. 2. Reject a missing argument with a usage message on stderr and exit status 2. 3. Read lines from stdin and print only the non-empty lines. 4. Display the current Python executable and major/minor version. 5. Refactor argument handling into a testable main(arguments) function.
Explore more Python tasks in the [interactive coding exercises](/codingexercises).