← Home

Ch 8 — Classes & Comprehensions

Ch 8 — Classes & Comprehensions

Aryan’s RAM manager now has dozens of functions — but they all share the same data and there’s no clean way to model a “process record” or a “snapshot”. Classes give that structure. Comprehensions collapse five-line loops into one expressive line, making the data-wrangling code that feeds the dashboard elegant and fast.


Classes

A class is a blueprint for objects. Each object (instance) bundles data (attributes) and behavior (methods).

class ProcessSnapshot:
    """Represents one RAM reading for a single process."""

    def __init__(self, pid, name, rss_mb):
        self.pid    = pid
        self.name   = name
        self.rss_mb = rss_mb

    def rss_gb(self):
        return self.rss_mb / 1024

    def __repr__(self):
        return f"ProcessSnapshot(pid={self.pid}, name={self.name!r}, rss_mb={self.rss_mb})"

Creating instances

chrome = ProcessSnapshot(4821, "chrome", 1024.5)
print(chrome.name)       # 'chrome'
print(chrome.rss_gb())   # 1.0009765625
print(chrome)            # ProcessSnapshot(pid=4821, name='chrome', rss_mb=1024.5)

__init__ and Instance Methods

  • __init__ is the initializer — runs automatically when you call ProcessSnapshot(...).
  • self is the first parameter of every instance method and refers to the calling object.
  • Dunder methods (__repr__, __str__, __eq__) let you customize how Python handles your objects.
class ProcessSnapshot:
    def __init__(self, pid, name, rss_mb):
        self.pid    = pid
        self.name   = name
        self.rss_mb = rss_mb

    def is_heavy(self, threshold_mb=512):
        return self.rss_mb > threshold_mb

Inheritance

One class can extend another, inheriting all its attributes and methods.

class CriticalProcess(ProcessSnapshot):
    """A process flagged as a memory hog."""

    def __init__(self, pid, name, rss_mb, reason):
        super().__init__(pid, name, rss_mb)
        self.reason = reason

    def alert_msg(self):
        return (
            f"ALERT: {self.name} (PID {self.pid}) using "
            f"{self.rss_mb:.1f} MB — {self.reason}"
        )

super().__init__(...) delegates to the parent class so you don’t duplicate code.


List Comprehensions

Build a new list by applying an expression to each element of an iterable.

# Old way
rss_values = []
for snap in snapshots:
    rss_values.append(snap.rss_mb)

# Comprehension
rss_values = [snap.rss_mb for snap in snapshots]

# With filter
heavy = [snap for snap in snapshots if snap.rss_mb > 512]

# Transform + filter
heavy_names = [snap.name.upper() for snap in snapshots if snap.rss_mb > 512]

Dict Comprehensions

# Build {pid: rss_mb} lookup
pid_rss = {snap.pid: snap.rss_mb for snap in snapshots}
# {4821: 1024.5, 312: 128.0, 9001: 512.0}

# Normalize process names
name_map = {snap.pid: snap.name.lower() for snap in snapshots}

enumerate and zip

# Numbered table rows
for i, snap in enumerate(snapshots, start=1):
    print(f"{i:>3}. {snap.name:<20} {snap.rss_mb:>8.1f} MB")

# Pair current vs previous snapshot
for prev, curr in zip(snapshots[:-1], snapshots[1:]):
    delta = curr.rss_mb - prev.rss_mb
    print(f"{curr.name}: Δ {delta:+.1f} MB")

sorted and sort

# sorted — returns a new list, non-destructive
top5 = sorted(snapshots, key=lambda s: s.rss_mb, reverse=True)[:5]

# sort — in-place
snapshots.sort(key=lambda s: s.name)

Use sorted() when you need the original order intact; use .sort() when in-place is fine.


map and filter

Functional alternatives to comprehensions.

# map — apply a function to every element
rss_list = list(map(lambda s: s.rss_mb, snapshots))

# filter — keep elements matching a predicate
heavy = list(filter(lambda s: s.rss_mb > 512, snapshots))

Comprehensions are generally preferred for readability, but map/filter appear in existing codebases you will maintain.


Class Hierarchy

flowchart TD
    A[ProcessSnapshot] --> B[__init__\npid name rss_mb]
    A --> C[rss_gb method]
    A --> D["__repr__"]
    A --> E[is_heavy method]
    F[CriticalProcess] -->|inherits| A
    F --> G[reason attribute]
    F --> H[alert_msg method]

Comprehension Patterns

flowchart LR
    A[Source iterable] --> B{Comprehension type}
    B --> C["[expr for x in it]"]
    B --> D["{k: v for x in it}"]
    B --> E["{expr for x in it} — set"]
    B --> F["(expr for x in it) — generator"]
    C --> G[Add filter with 'if cond']
    D --> G
    E --> G

Key Takeaways

  • Classes bundle data (attributes) and behavior (methods) into reusable blueprints.
  • __init__ is the initializer; self refers to the instance — always the first method parameter.
  • Inheritance via super() lets child classes reuse and extend parent behavior.
  • Dunder methods (__repr__, __str__, __eq__) integrate custom classes with Python’s protocols.
  • List comprehensions replace simple for-append loops with a single, readable expression.
  • Dict comprehensions build lookup tables in one line.
  • sorted() is non-destructive; .sort() is in-place — know which you need.
  • map() and filter() are functional alternatives; comprehensions are usually more Pythonic.