← Home

139. Functions + Dicts + Modules (App V0.3)

RAM Manager v0.3 — clean functions and structured data

139. Functions + Dicts + Modules (App V0.3)

His main() is 60 lines long. Everything is tangled together.

🤔 Socratic question: If every function does everything, can you test anything in isolation? Can you reuse anything? Can a teammate understand what changed? This is why senior devs obsess over “single responsibility” — not because it sounds smart, but because it makes everything easier.

He spots the problem: he’s using global variables and mutating them instead of returning values.

# Bad: global mutation
snapshot = {}
def collect():
    global snapshot
    snapshot['percent'] = mem.percent  # side effect — not testable

# Good: return a value
def take_snapshot() -> dict:
    return {'percent': mem.percent, ...}  # pure function
snapshot = take_snapshot()

He refactors into focused functions, each with one job:

main() ├── take_snapshot() → dict ├── is_critical(snapshot) → bool ├── format_summary(snapshot) → str └── format_processes(processes) → str

Each function takes its inputs as parameters and returns its output. Nothing reads a global. Each can be tested in isolation with fake data.

🧠 The golden rule: A function should do ONE thing. If you have to use the word “and” to describe what it does, split it. collect_and_format_and_print → bad. collect(), format(), print() → good.

💡 Real-world: Google’s “clean code” guidelines, Amazon’s internal engineering docs, every tech company’s style guide — they all say the same thing. Pure functions. No hidden state. One job per function. Not because it’s pretty — because bugs disappear.

💡 Fun fact: The term “pure function” comes from functional programming — a field influenced by mathematical functions. In math, f(x) always returns the same result for the same x. A pure Python function does the same: same inputs, same output, no side effects. Languages like Haskell enforce this by making all state changes explicit. Python doesn’t force it, but senior engineers choose it because pure functions are trivially testable.

⚠️ Watch out: The global keyword in Python is a code smell. Every time you use global, you create hidden coupling — any function can modify the variable, so bugs become hard to trace. If you find yourself writing global, it’s almost always a sign that the function should take a parameter and return a value instead.

🤔 Think about it: format_summary() that reads a global snapshot can only ever format that one global. format_summary(snapshot) can format any snapshot dict you give it — you can call it in tests, in loops, anywhere. How does this change scale?


🧹 Aryan’s RAM manager works but main() is 40 lines long. He refactors into focused functions, but forgets to return values (mutates instead), uses global variables where parameters belong, and names things inconsistently.

Learning objectives

  • Return values instead of mutating globals
  • Pass data as function parameters (not global reads)
  • Break main() into single-responsibility functions
  • Write functions that are testable in isolation

Key concepts

  • return value — function output
  • parameters — data passed to a function
  • global keyword — avoid it (mutable global state)
  • single responsibility — one function, one job

Try it

Concept detail

App v0.3 — Functions and structured data

Core refactoring principle

# Bad: function mutates global
result = {}
def compute():
    global result
    result['x'] = 42   # side effect, not testable

# Good: function returns value
def compute() -> dict:
    return {'x': 42}  # pure, testable, reusable
result = compute()

Passing data as parameters

# Bad: reads global — can't test in isolation
def format_summary():
    return f"RAM: {snapshot['percent']:.1f}%"

# Good: takes parameter — testable with any data
def format_summary(snapshot: dict) -> str:
    return f"RAM: {snapshot['percent']:.1f}%"

What v0.3 looks like now

main()
  ├── take_snapshot() → dict
  ├── is_critical(snapshot) → bool
  ├── format_summary(snapshot) → str
  └── format_processes(processes) → str

Each function does one thing. Each takes inputs, returns outputs.

Solution

import psutil

THRESHOLD = 80

def take_snapshot(n: int = 10) -> dict:
    '''Collect current RAM stats and top-N processes.'''
    mem = psutil.virtual_memory()
    processes = []
    for proc in psutil.process_iter(['pid', 'name', 'memory_info']):
        try:
            rss_mb = proc.info['memory_info'].rss / 1e6
            processes.append({
                'name': proc.info['name'],
                'pid': proc.info['pid'],
                'rss_mb': rss_mb,
            })
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            pass

    return {
        'percent': mem.percent,
        'used_gb': mem.used / 1e9,
        'total_gb': mem.total / 1e9,
        'available_gb': mem.available / 1e9,
        'processes': sorted(processes, key=lambda p: p['rss_mb'], reverse=True)[:n],
    }

def is_critical(snapshot: dict, threshold: int = THRESHOLD) -> bool:
    '''Return True if RAM usage exceeds the threshold.'''
    return snapshot['percent'] > threshold

def format_summary(snapshot: dict) -> str:
    '''Return a one-line RAM usage summary.'''
    return (
        f"RAM: {snapshot['percent']:.1f}%  "
        f"({snapshot['used_gb']:.1f}/{snapshot['total_gb']:.1f} GB)"
    )

def format_processes(processes: list[dict], n: int = 5) -> str:
    '''Return a formatted table of top processes.'''
    lines = [f"{'Process':<20} {'PID':<8} {'RSS MB':>8}"]
    lines.append('-' * 38)
    for p in processes[:n]:
        lines.append(f"{p['name']:<20} {p['pid']:<8} {p['rss_mb']:>8.0f}")
    return '\n'.join(lines)

def main():
    snapshot = take_snapshot(n=10)
    print(format_summary(snapshot))
    if is_critical(snapshot):
        print(f'⚠️  RAM above {THRESHOLD}% — top processes:')
    print(format_processes(snapshot['processes'], n=5))

if __name__ == '__main__':
    main()

Tests

import inspect
from unittest.mock import patch, MagicMock

def _make_proc(name, pid, rss_bytes):
    proc = MagicMock()
    mem_info = MagicMock()
    mem_info.rss = rss_bytes
    proc.info = {'name': name, 'pid': pid, 'memory_info': mem_info}
    return proc

def _make_mock_mem(percent=72.5, used=11_600_000_000, total=16_000_000_000, available=4_400_000_000):
    mem = MagicMock()
    mem.percent = percent
    mem.used = used
    mem.total = total
    mem.available = available
    return mem

MOCK_PROCS = [
    _make_proc('chrome', 812, 1_800_000_000),
    _make_proc('python', 421, 670_000_000),
]

def test_take_snapshot_returns_dict():
    with patch('psutil.virtual_memory', return_value=_make_mock_mem()), \
         patch('psutil.process_iter', return_value=iter(MOCK_PROCS)):
        snap = take_snapshot()
    assert isinstance(snap, dict)
    assert 'percent' in snap
    assert 'processes' in snap

def test_take_snapshot_no_global_mutation():
    src = inspect.getsource(take_snapshot)
    assert 'global' not in src, 'take_snapshot must not use global variables'

def test_is_critical_takes_snapshot_param():
    snap = {'percent': 85.0}
    assert is_critical(snap, threshold=80) is True
    snap2 = {'percent': 70.0}
    assert is_critical(snap2, threshold=80) is False

def test_format_summary_takes_snapshot_param():
    snap = {'percent': 72.5, 'used_gb': 11.6, 'total_gb': 16.0}
    summary = format_summary(snap)
    assert '72.5' in summary
    assert '11.6' in summary

def test_format_processes_takes_list_param():
    procs = [
        {'name': 'chrome', 'pid': 812, 'rss_mb': 1800.0},
        {'name': 'python', 'pid': 421, 'rss_mb': 670.0},
    ]
    table = format_processes(procs, n=2)
    assert 'chrome' in table
    assert 'python' in table

def test_no_global_snapshot_variable():
    src = inspect.getsource(main)
    # main should call take_snapshot() and use the return value
    assert 'take_snapshot()' in src or 'take_snapshot(n' in src

Resources