Sys Module

Python Modules

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:

  • 0 means 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 0 after an error.
  • Calling sys.exit() inside reusable library code.
  • Mixing diagnostics into standard output.
  • Parsing the human-readable sys.version string.
  • 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).