Chapter 3 · Writing Real Programs
Classes and Objects
- Page 12 of 20
- 4 min read
So far data and the functions that work on it have lived separately. A class bundles them together: it describes a kind of thing — a conversation, a model, a document — with the data it holds and the actions it can do. Each concrete thing made from the class is an object (or instance). Every AI library you will use is built out of classes: OpenAI() creates a client object, and client.responses.create(...) calls a method on it.
A class from scratch
class Conversation:
"""A chat history that can be sent to a language model."""
def __init__(self, system_prompt):
self.messages = [{"role": "system", "content": system_prompt}]
def add_user(self, text):
self.messages.append({"role": "user", "content": text})
def add_assistant(self, text):
self.messages.append({"role": "assistant", "content": text})
def last_reply(self):
for message in reversed(self.messages):
if message["role"] == "assistant":
return message["content"]
return None
def __len__(self):
return len(self.messages)
def __repr__(self):
return f"Conversation({len(self)} messages)"
chat = Conversation("You are a patient Python tutor.")
chat.add_user("What is a list?")
chat.add_assistant("An ordered, changeable collection of items.")
print(chat)
print(chat.last_reply())
other = Conversation("You translate English into Bangla.")
print(len(chat), len(other)) # each object has its own messagesConversation(3 messages)
An ordered, changeable collection of items.
3 1Step by step:
class Conversation:defines the class. Class names useCapitalisedWords.__init__runs automatically when you create an object withConversation("..."). It sets up the object's starting data.selfis the object itself.self.messagesis an attribute — data stored on this particular object. That is whychatandothereach have their own messages.- Functions defined inside a class are methods. They always take
selffirst, but you do not pass it:chat.add_user("…")fills inselffor you. - Names with double underscores, like
__len__and__repr__, are special methods that plug your class into Python:len(chat)calls__len__, andprint(chat)uses__repr__.
Inheritance: a family of classes
A class can inherit from another, getting all its attributes and methods, and then change (override) the parts that differ:
class Model:
def __init__(self, name):
self.name = name
def describe(self):
return f"{self.name}: {self.kind()}"
def kind(self):
return "a generic model"
class ChatModel(Model):
def kind(self): # override the parent's version
return "turns messages into a reply"
class EmbeddingModel(Model):
def kind(self):
return "turns text into a list of numbers"
for m in [ChatModel("gpt-5.5"), EmbeddingModel("text-embedding-3-small")]:
print(m.describe())
print(isinstance(ChatModel("x"), Model))gpt-5.5: turns messages into a reply
text-embedding-3-small: turns text into a list of numbers
Truedescribe() is written once, in Model, yet each subclass describes itself correctly, because self.kind() calls the version belonging to the actual object. You already used inheritance on the previous page: class TemporaryAPIError(Exception).
Dataclasses: classes that mainly hold data
Many classes just hold a few named values — settings, a search result, a parsed answer. The @dataclass decorator writes __init__, __repr__ and == for you from the field list:
from dataclasses import dataclass, field
@dataclass
class RequestSettings:
model: str
temperature: float = 0.7
max_output_tokens: int = 500
stop: list[str] = field(default_factory=list)
def cheaper(self):
return RequestSettings(self.model, self.temperature, self.max_output_tokens // 2, self.stop)
s = RequestSettings("gpt-5.5", temperature=0.2)
print(s)
print(s.cheaper())
print(s == RequestSettings("gpt-5.5", 0.2))RequestSettings(model='gpt-5.5', temperature=0.2, max_output_tokens=500, stop=[])
RequestSettings(model='gpt-5.5', temperature=0.2, max_output_tokens=250, stop=[])
TrueThe : str, : float after each field are type hints (more on page 18). For a list default, use field(default_factory=list) — the same mutable-default trap you met on page 7.
When should you write a class?
- Some data and the functions on it always travel together (a conversation and its messages) — write a class.
- You need several similar objects with their own state — write a class.
- You just have a calculation that takes input and returns output — a function is enough. Not everything needs to be a class.
Try it yourself
- Add a method
word_count()toConversationthat returns the total number of words in all messages. - Write a dataclass
SearchResultwithtitle,urlandscore, make three of them and print the best by score. - Create a
BanglaTutorclass that inherits fromConversationand always starts with a Bangla system prompt.