← Home

Ch 7 — Functions & Exceptions

Ch 7 — Functions & Exceptions

Aryan’s RAM manager is growing. The polling loop, the formatting logic, and the alert system are all jumbled into one long script. Functions let him carve that script into named, reusable pieces. Exceptions let him handle the inevitable failures — a dead process, a missing config file, a psutil permission error — without crashing the whole monitor.


Defining Functions

def get_ram_percent(used_mb, total_mb):
    """Return RAM usage as a percentage (0–100)."""
    return (used_mb / total_mb) * 100

Call it:

pct = get_ram_percent(10_240, 16_384)   # 62.5

Parameters, Defaults & Keyword Arguments

def format_row(name, rss_mb, pct, width=20):
    """Format a single process row for display."""
    return f"{name:<{width}} {rss_mb:>8.1f} MB  {pct:>6.1f}%"

# Positional
format_row("chrome", 1024.5, 73.5)

# Keyword — order doesn't matter
format_row(rss_mb=512.0, name="electron", pct=36.7)

# Override default
format_row("vscode", 256.0, 18.3, width=30)

*args and **kwargs

Accept variable numbers of positional or keyword arguments.

def log(*messages, level="INFO"):
    """Log one or more messages."""
    for msg in messages:
        print(f"[{level}] {msg}")

log("RAM OK", "CPU OK")                       # INFO level
log("RAM critical", level="WARN")

def build_snapshot(**fields):
    return fields   # returns dict of whatever was passed

snap = build_snapshot(name="chrome", pid=4821, rss_mb=1024)

Lambda

Anonymous one-line functions — great as sort keys.

processes = [
    {"name": "chrome",   "rss_mb": 1024},
    {"name": "electron", "rss_mb": 512},
    {"name": "python",   "rss_mb": 64},
]

# Sort by RAM usage descending
sorted_procs = sorted(processes, key=lambda p: p["rss_mb"], reverse=True)

Recursion

A function that calls itself. Useful for tree-like process hierarchies.

def sum_rss(proc_tree):
    """Sum RSS across a tree of child processes."""
    total = proc_tree.get("rss_mb", 0)
    for child in proc_tree.get("children", []):
        total += sum_rss(child)
    return total

Be careful of deep recursion — Python’s default limit is 1000 frames.


Importing Modules

import psutil                        # full module
import time                          # standard library

from pathlib import Path             # import specific name
from datetime import datetime, timedelta

# Alias for brevity
import json as j

Standard Library Highlights for RAM Manager

import os, sys, time, json, re
from pathlib import Path
from collections import defaultdict

__name__ == '__main__'

Guards code that should only run when the file is executed directly, not when imported.

def main():
    print("Starting RAM Manager…")
    poll_forever()

if __name__ == "__main__":
    main()

Without this guard, main() would run every time another script imports your module.


Raising Exceptions

Signal errors explicitly.

def read_config(path):
    if not Path(path).exists():
        raise FileNotFoundError(f"Config not found: {path}")
    # ...

def validate_threshold(value):
    if not 0 < value <= 100:
        raise ValueError(f"Threshold must be 1–100, got {value}")

try / except / finally

Handle errors gracefully instead of crashing.

try:
    proc = psutil.Process(pid)
    rss  = proc.memory_info().rss / (1024 ** 2)
except psutil.NoSuchProcess:
    rss = 0.0
    print(f"PID {pid} vanished before we could read it")
except psutil.AccessDenied:
    rss = 0.0
    print(f"PID {pid} — permission denied (run as root?)")
except Exception as e:
    rss = 0.0
    print(f"Unexpected error reading PID {pid}: {e}")
finally:
    log_attempt(pid)   # always runs — even if an exception occurred

Multiple except clauses

Handle specific exceptions first, broad ones last. Never use a bare except: — it swallows KeyboardInterrupt and SystemExit.


Function Design Map

flowchart LR
    A[Function] --> B[Signature]
    B --> C[Positional params]
    B --> D[Default params]
    B --> E["*args — variadic positional"]
    B --> F["**kwargs — variadic keyword"]
    A --> G[Body]
    G --> H[Logic]
    G --> I[return value]
    A --> J[Lambda for simple 1-liners]

Exception Handling Flow

flowchart TD
    A[try block] --> B{Exception raised?}
    B -->|No| C[Continue normally]
    B -->|Yes| D{Match except clause?}
    D -->|NoSuchProcess| E[Handle missing PID]
    D -->|AccessDenied| F[Handle permissions]
    D -->|Other Exception| G[Log and continue]
    E --> H[finally block — always runs]
    F --> H
    G --> H
    C --> H
    H --> I[Next iteration]

Key Takeaways

  • Functions decompose a script into reusable, testable units — define them with def, document them with docstrings.
  • Default parameters simplify calls; keyword arguments improve readability at the call site.
  • *args collects extra positional arguments; **kwargs collects extra keyword arguments.
  • Lambda creates tiny anonymous functions — ideal as sort keys.
  • if __name__ == "__main__" prevents entry-point code from running on import.
  • raise signals errors explicitly; use specific exception types with descriptive messages.
  • try / except / finally keeps the monitor alive through transient failures (missing PIDs, permission errors).
  • Always catch the most specific exception first; never use bare except:.