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) * 100Call it:
pct = get_ram_percent(10_240, 16_384) # 62.5Parameters, 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 totalBe 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 jStandard 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 occurredMultiple 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.
*argscollects extra positional arguments;**kwargscollects extra keyword arguments.- Lambda creates tiny anonymous functions — ideal as sort keys.
if __name__ == "__main__"prevents entry-point code from running on import.raisesignals errors explicitly; use specific exception types with descriptive messages.try / except / finallykeeps the monitor alive through transient failures (missing PIDs, permission errors).- Always catch the most specific exception first; never use bare
except:.