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:

>>> a bank account
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)
Output
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:

>>> two separate accounts
account1 = BankAccount("Jamie", 100)
account2 = BankAccount("Sasha", 500)

account1.deposit(25)

print(account1.owner, account1.balance)
print(account2.owner, account2.balance)
Output
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:

>>> withdrawing safely
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))
Output
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.

Note: nothing here stops other code from writing 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.
Course complete: that covers the full Python course — variables, data types, strings, numbers and operators, lists, dictionaries, conditionals, loops, functions, tuples and sets, reading and writing files, and finally packaging data and behavior together into classes and objects. From here, the natural next steps are exploring Python's standard library, third-party packages, and the frameworks — like Flask, Django, and pandas — built on exactly the fundamentals covered in this course.