Classes & Objects
Everything so far has dealt with loose variables and standalone functions. A class lets you bundle related data and behavior together into one blueprint. An object is a specific thing built from that blueprint, with its own independent copy of the data.
Defining a class
class starts a class definition. __init__ is a special method that runs automatically when a new object is created, and self refers to the specific object being worked with:
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
print("Deposited " + str(amount) + ". New balance: " + str(self.balance))
account = BankAccount("Jamie", 100)
account.deposit(50)
Deposited 50. New balance: 150
self.owner and self.balance are attributes — the data each account carries. deposit is a method that belongs to the class, and inside it, self.balance refers to that particular object's balance without needing any extra qualification. Every method needs self as its first parameter, even though you never pass it explicitly when calling account.deposit(50) — Python fills it in for you.
One blueprint, many objects
Calling BankAccount(...) as many times as you like produces completely independent objects. Changing one account's balance never touches another's:
account1 = BankAccount("Jamie", 100)
account2 = BankAccount("Sasha", 500)
account1.deposit(25)
print(account1.owner, account1.balance)
print(account2.owner, account2.balance)
Deposited 25. New balance: 125 Jamie 125 Sasha 500
Depositing into account1 left account2's balance of 500 completely undisturbed — they're separate objects, even though they came from the same class.
A method that returns a result
A withdrawal needs to fail gracefully when there isn't enough money — a good fit for a method that returns True or False to tell the caller whether it worked:
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
print("Insufficient funds.")
return False
self.balance -= amount
return True
account = BankAccount("Jamie", 100)
print(account.withdraw(30))
print(account.balance)
print(account.withdraw(200))
True 70 Insufficient funds. False
The first withdrawal succeeds, drops the balance to 70, and returns True. The second tries to take out more than what's left, prints a message, and returns False without touching the balance at all.
account.balance = -9999 directly, bypassing withdraw() entirely — Python attributes are public by default, with no strict equivalent of a private field. By convention, a name prefixed with a single underscore (self._balance) signals "internal, please don't touch this directly," but it's a hint for other programmers, not an enforced restriction.