Classes & Objects
A class is defined with the class keyword and acts as the blueprint for a new type of object. An object (or instance) is what you get when you call the class like a function — each call produces a separate, independent value with its own copy of whatever data the class defines. This page walks through the syntax for both, and spends extra time on self, the one piece of the puzzle that trips up almost everyone the first time they see it.
The `class` keyword and creating instances
A minimal class needs nothing more than the keyword, a name, and a body. Even an empty class can be instantiated — calling the class name with parentheses creates a new object.
class Dog:
pass
my_dog = Dog()
print(type(my_dog))
print(my_dog)<class '__main__.Dog'> <__main__.Dog object at 0x7f2c1a3b4d90>
my_dog is an object — a real, distinct value living in memory. The memory address will differ every time you run this, but the important part is that calling Dog() produced something new, not a reference to the class itself.
Setting instance attributes inside methods
A class on its own is just a container for methods (functions defined inside it). To give each object its own data, methods set instance attributes — values attached to that specific object — using self.attribute_name = value. The most common place to do this is inside __init__, which we'll cover in full on the next page, but any method can create or update instance attributes.
class Dog:
def set_name(self, name):
self.name = name
my_dog = Dog()
my_dog.set_name("Rex")
print(my_dog.name)Rex
Understanding `self`
self is the first parameter of every regular method in a class, and it refers to the specific object the method was called on. Python fills this parameter in automatically — you never pass it yourself. When you write my_dog.set_name("Rex"), Python translates that call behind the scenes into something equivalent to Dog.set_name(my_dog, "Rex"). The object you called the method on becomes self inside the method body.
What you write | What Python actually does |
|---|---|
| Calls |
inside the method: | Sets the |
This is why self.name = name inside a method changes only the object the method was called on, and not every Dog in existence: self is that one specific object, not the class.
class Dog:
def bark(): # missing self!
print("Woof!")
my_dog = Dog()
my_dog.bark()TypeError: Dog.bark() takes 0 positional arguments but 1 was given
Worked example: a BankAccount class
Let's put all of this together in a class that is actually useful: a simple bank account with a balance, a way to deposit money, a way to withdraw money that refuses to overdraw the account, and a way to check the current balance.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if amount > self.balance:
print(f"Cannot withdraw {amount}: insufficient funds")
return
self.balance -= amount
def get_balance(self):
return self.balanceNow let's create two separate accounts and show that each one keeps its own, independent balance:
alice_account = BankAccount("Alice", 100)
bob_account = BankAccount("Bob", 50)
alice_account.deposit(200)
bob_account.withdraw(20)
print(alice_account.get_balance())
print(bob_account.get_balance())
bob_account.withdraw(1000)300 30 Cannot withdraw 1000: insufficient funds
Notice that depositing into alice_account had zero effect on bob_account.balance. BankAccount is the blueprint, defined exactly once in the code above. alice_account and bob_account are two separate objects built from it, each with its own owner and balance living independently in memory. That independence — one object's state never leaking into another's — is the entire point of instance attributes.
Class vs. object, side by side
The class (
BankAccount) is the definition — written once, describing what every account will be able to do.An object (
alice_account,bob_account) is one instance created by callingBankAccount(...)— you can create as many as you want, each independent.Methods (
deposit,withdraw,get_balance) are defined once on the class but act on whichever object called them, viaself.Instance attributes (
self.owner,self.balance) are created per object, so each instance has its own copy.