Classes and objects
A class is a blueprint; an object is one thing built from it. Trace two train tickets being created from one TrainTicket class and see what self, __init__ and attributes really do.
So far data and the functions that work on it have lived apart: a dict of ticket details here, a fare() function there. A class bundles them. It describes what every ticket has (attributes, such as the passenger and the distance) and what every ticket can do (methods, such as working out its fare). Each ticket you create from the class is an object, also called an instance.
Two names do most of the work. __init__ is the method Python calls automatically when you create an object; it is where you store the starting values. self is the object itself, passed in as the first parameter of every method so the method knows which ticket it is working on. When you write t2.fare(), Python quietly turns it into TrainTicket.fare(t2).
You have been using classes all along: int, str, list and dict are classes, and "hi".upper() is a method call. Agent frameworks are built the same way. Agent(...) in ADK creates an object from the Agent class, and tool_context.state reads an attribute.
Press Run to trace train_ticket.py. Watch the Variables panel: attributes appear as t1.passenger and t2.km, each belonging to one object.
# A TrainTicket class: one blueprint, many tickets.
class TrainTicket:
fare_per_km = 2 # class attribute, shared by every ticket
def __init__(self, passenger, origin, destination, km):
self.passenger = passenger # instance attributes: one set per object
self.origin = origin
self.destination = destination
self.km = km
self.confirmed = False
def fare(self):
return self.km * TrainTicket.fare_per_km
def confirm(self):
self.confirmed = True
return f"{self.passenger}: {self.origin} to {self.destination}, Rs {self.fare()}"
t1 = TrainTicket("Asha", "Chennai", "Madurai", 460)
t2 = TrainTicket("Ravi", "Pune", "Mumbai", 150)
print(t1.passenger, t1.confirmed)
print(t2.fare())
print(t1.confirm())
print(t1.confirmed, t2.confirmed)
print(type(t1).__name__)Press Play to run the program line by line, or step with ← →.
Variables
Nothing defined yet.
Output
Nothing printed yet.
What to notice
- The class is built once; objects are built per call.
class TrainTicket:runs once and creates the blueprint.TrainTicket("Asha", ...)runs__init__and creates a fresh object each time. self.x = ...stores on the object;x = ...would be a local that vanishes. The arguments to__init__are gone when it returns. Only what you attached toselfsurvives, ast1.passengerand friends.- Class attributes are shared; instance attributes are per object.
fare_per_kmis written once in the class body and read by every ticket.confirmedis set in__init__, so each ticket has its own. Changingt1.confirmedleavest2alone. - Methods receive the object as
self. You never pass it yourself. The nameselfis a convention, not a keyword, but every Python programmer expects it. - A method can change state or just answer a question.
confirm()setsself.confirmed = True;fare()only computes. Both read attributes throughself. type(obj)gives the class. Everything in Python is an object of some class, so this works on your tickets and on3alike.
Why this matters for AI work
Reading agent code means reading objects. root_agent = Agent(name="support_agent", model="gemini-flash-latest", tools=[...]) is a class being called with keyword arguments that __init__ stores as attributes. tool_context.state["last_order_id"] = order_id reaches into an object you were handed. Events, sessions, tool results and Pydantic models are all objects with attributes and methods. Once self and __init__ feel ordinary, framework code stops looking like magic.