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) → strEach 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