Skip to main content

Classes and objects: bundling data with behavior

You have actually been using objects this whole course without naming them. A string is an object; that's why "hello".upper() works — upper is a method (a function attached to the object). A list is an object; nums.append(3) calls the list's append method. This lesson reveals what was behind that dot all along, and teaches you to make your own objects. You don't need to become an object-oriented wizard — but the next guides hand you objects constantly (an API client, a request, a response, a model), and to use them you must be able to read a class and create instances of it. That's the goal.

Why objects exist

Picture modeling a dog in a program. A dog has data — a name, an age — and it can do things — bark, have a birthday. With what you know so far, you'd scatter that across loose variables and functions:

dog_name = "Rex"
dog_age = 3

def bark(name):
return name + " says Woof!"

def have_birthday(age):
return age + 1

This works for one dog. For fifty dogs it falls apart: fifty name/age variable pairs, and functions that don't know which dog they belong to. The data and the behavior that belong together are floating apart. An object bundles them. A Dog object carries its own name and age and knows how to bark — data and behavior packaged into one self-contained thing. That bundling is the whole point of objects.

:::note The two words to keep straight A class is the blueprint — it describes what every dog has and can do, written once. An object (also called an instance) is a real dog built from that blueprint. One Dog class; as many dog objects as you like, each with its own name and age. Class = cookie cutter; object = each cookie. :::

Defining a class

Here is a Dog class. Read it once, top to bottom — then we'll dissect every piece:

class Dog:
def __init__(self, name, age):
self.name = name # store this dog's name on the object
self.age = age # store this dog's age on the object

def bark(self):
return self.name + " says Woof!"

def have_birthday(self):
self.age = self.age + 1

And here is how you use it — create an instance and interact with it:

rex = Dog("Rex", 3) # build a Dog object; __init__ runs with name="Rex", age=3
print(rex.name) # Rex — read an attribute
print(rex.bark()) # Rex says Woof! — call a method
rex.have_birthday() # changes rex's own age
print(rex.age) # 4

Now the pieces, one at a time.

class

class Dog:

The class keyword starts a blueprint, followed by the class name (capitalized by convention — Dog, BankAccount, HttpClient). Everything indented under it belongs to the class.

init: the setup method

def __init__(self, name, age):
self.name = name
self.age = age

__init__ (say "dunder init," short for "double underscore init") is a special method Python calls automatically the moment you create an object. Its job is to initialize — to set up the new object's starting data. When you write Dog("Rex", 3), Python builds a blank dog and immediately runs __init__ with name="Rex" and age=3. You never call __init__ directly; creating the object calls it for you.

self: the object itself

This is the one piece that trips up every beginner, so go slowly. self is the object the method is working on. When you call rex.bark(), Python automatically passes rex in as self. So inside bark, self is rex — and self.name means "this particular dog's name."

  • self is the first parameter of every method, always. You write it in the def, but you never pass it yourself — Python fills it in from whatever object is before the dot. rex.bark() becomes bark(rex) behind the scenes, with rex landing in self.
  • self.name = name means "store the incoming name on this object, under its own name attribute." That's how each dog remembers its own data separately.
rex = Dog("Rex", 3)
fido = Dog("Fido", 7)
print(rex.bark()) # Rex says Woof! — self is rex, self.name is "Rex"
print(fido.bark()) # Fido says Woof! — self is fido, self.name is "Fido"

Same bark method, two different objects, two different results — because self points at whichever dog you called it on. That is the magic the dot was hiding.

:::tip One sentence for self self means "the object this method was called on." rex.bark() runs bark with self = rex, so every self.something reads or writes rex's own data. You declare self but never pass it — the dot before the method name supplies it. :::

Attributes and methods

Two more terms, now that you've seen them in action:

  • An attribute is a piece of data stored on an object — rex.name, rex.age. You read or set it with the dot: rex.age gets it, rex.age = 4 sets it.
  • A method is a function defined inside the class — bark, have_birthday. You call it with the dot and parentheses: rex.bark(). A method usually works with the object's attributes through self.

have_birthday shows a method changing its object's data: self.age = self.age + 1 reads this dog's current age and stores the incremented value back on the same dog. Call rex.have_birthday() and only rex's age goes up — fido is untouched.

A second, more "real" example

Classes shine for things with state that changes over time — a bank account, a shopping cart, a game character. Here's an account, which is much closer to the objects you'll meet in the next guides:

class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance # default starts at 0

def deposit(self, amount):
self.balance = self.balance + amount

def withdraw(self, amount):
if amount > self.balance:
raise ValueError("insufficient funds") # refuse, don't go negative
self.balance = self.balance - amount

def summary(self):
return self.owner + " has $" + str(self.balance)

acct = BankAccount("Ada") # balance defaults to 0
acct.deposit(100)
acct.withdraw(30)
print(acct.summary()) # Ada has $70
print(acct.balance) # 70

Notice the familiar tools all reappear inside a class: a default parameter (balance=0), a method calling raise to refuse bad input, string conversion with str(). A class doesn't replace what you know — it organizes it around a thing.

You'll mostly READ classes, not write them

Be honest about the goal. In the guides ahead you will rarely design elaborate class hierarchies. What you'll do constantly is use objects other people defined:

# Modern AI guide (illustrative): an SDK gives you a client OBJECT
client = Anthropic(api_key="...") # create an instance
response = client.messages.create(...) # call a method on it
print(response.content) # read an attribute off the result

You can already read every line of that: create an instance, call a method with the dot, read an attribute. That's the entire payoff of this lesson — the object-oriented surface of every modern library is now legible to you.

Why it matters

Objects are how nearly all real software is organized, because bundling data with the behavior that acts on it scales in a way loose variables never do. You don't have to love OOP or design deep hierarchies — but class, __init__, self, attributes, and methods are the vocabulary of every SDK, framework, and library you're about to touch. When the AI guide hands you a client and the web guide hands you a request, you'll know exactly what they are: objects, made from a class, carrying their own data and methods. No magic left.

Where this leads: SDK clients, request/response objects, and framework models are all just instances of classes — which you can now read fluently. See Where this leads next.

Practice

Define a small class and exercise it. The test calls a method that returns a value:

⌨️ Challenge — Python · practice (not graded)

Define a class Counter with __init__ that sets self.count = 0, a method increment() that adds 1 to self.count, and a method value() that returns self.count. Then complete run_counter(n): create a Counter, call increment() n times, and return its value(). run_counter(3) should return 3; run_counter(0) returns 0.

Checkpoint

Required checkpoint

Classes and objects

Pass to unlock the Next button below

Next: Working with data: JSON →