Intermediate22 min

Classes and objects

Bundle state and behaviour together — the pattern behind PyTorch modules, retrievers, and LLM clients.

Why this matters in AI / ML / GenAI

PyTorch models subclass nn.Module. LangChain retrievers, tokenizers, and Hugging Face pipelines are classes you instantiate once and call many times. Understanding __init__, self, and inheritance makes those libraries readable instead of magic.

__init__, self, and attributes

A class is a template. An instance is one object built from it.

__init__ runs at construction and sets up attributes on self. self is just the instance, passed automatically — you write it in the definition, not at the call site.

Load expensive things once in __init__ (a model, a client, an index) and reuse them in methods. That is exactly why Hugging Face pipelines and vector-store clients are classes: constructing is slow, calling is fast.

Methods, __repr__, and __call__

Regular methods take self first. @staticmethod needs no instance; @classmethod receives the class and is commonly used for alternate constructors like Config.from_json(path).

__repr__ controls what you see when you print the object. Add one — debugging a list of nameless objects is miserable.

__call__ makes an instance callable like a function: model(inputs). That is why PyTorch code calls the module directly instead of model.forward(inputs).

Inheritance — use sparingly

A subclass reuses and extends a parent: class BM25Retriever(BaseRetriever):. Call super().__init__(...) to run the parent setup.

Frameworks are built on inheritance (nn.Module), so you must read it. In your own code, prefer composition: a RagPipeline that holds a retriever and a generator is easier to test and swap than a five-level class hierarchy.

Copy-paste examples

Copy into your own editor, or load one into the compiler below and press Run.

A retriever class

Index built once in __init__, reused on every search call.

class KeywordRetriever:
    def __init__(self, documents):
        self.documents = documents
        self.index = {i: set(d.lower().split()) for i, d in enumerate(documents)}

    def search(self, query, top_k=2):
        terms = set(query.lower().split())
        scored = []
        for i, words in self.index.items():
            overlap = len(terms & words)
            if overlap:
                scored.append((overlap, self.documents[i]))
        scored.sort(reverse=True)
        return [doc for _, doc in scored[:top_k]]

    def __repr__(self):
        return f"KeywordRetriever(n_docs={len(self.documents)})"

retriever = KeywordRetriever([
    "python powers machine learning pipelines",
    "kubernetes runs containers in production",
    "python serves llm apis with fastapi",
])
print(retriever)
for hit in retriever.search("python llm"):
    print("-", hit)

__call__ makes an object behave like a function

This is why PyTorch code writes model(x) rather than model.forward(x).

class Scaler:
    def __init__(self, factor):
        self.factor = factor

    def __call__(self, values):
        return [v * self.factor for v in values]

scale = Scaler(0.5)
print(scale([1.0, 2.0, 3.0]))
print(callable(scale))

Inheritance with super()

The subclass reuses parent setup and overrides one method.

class BaseGenerator:
    def __init__(self, model_name):
        self.model_name = model_name

    def generate(self, prompt):
        return f"[{self.model_name}] {prompt}"

class CautiousGenerator(BaseGenerator):
    def __init__(self, model_name, refusal="I do not know."):
        super().__init__(model_name)
        self.refusal = refusal

    def generate(self, prompt):
        if "context:" not in prompt.lower():
            return self.refusal
        return super().generate(prompt)

gen = CautiousGenerator("local-llm")
print(gen.generate("Context: RAG grounds answers. Question: what is RAG?"))
print(gen.generate("Just guess something"))

Build a token-budget tracker

Try it — in-browser Python

Add more calls until the budget is exceeded and see the guard trigger.

Output

Python runs in your browser. First run downloads the runtime.

Press Run (or Ctrl+Enter) to execute.

CPython in WebAssembly. Stdlib works. NumPy and pandas load on demand. No input(), no GPU, no network installs.

Takeaways

  • __init__ sets up state once; methods reuse it — the pattern behind model and client classes.
  • __call__ is why PyTorch modules are invoked like functions.
  • Read inheritance in frameworks, but prefer composition in your own code.