Python Classes and Objects: OOP from the Ground Up
Imagine you own a bakery and you have a cookie cutter shaped like a star. Every time you press it into dough, you get a new star-shaped cookie. The cutter itself isn't a cookie — it's a blueprint for making cookies.
In Python, a class is the cookie cutter, and each cookie you stamp out is an object. The class defines the shape (what data it holds) and the behavior (what it can do). Objects are the actual things you work with in your program.
In this tutorial, you'll learn how to create your own classes, give them data with __init__, add behavior with methods, and control how they display with __str__. By the end, you'll be building your own custom types in Python.
What Are Classes and Objects?
You've already used objects in Python without realizing it. A string like 'hello' is an object. A list like [1, 2, 3] is an object. Even integers are objects. Every value in Python is an object created from some class.
A class is a template that bundles together data (called attributes) and functions (called methods) into one package. An object is a specific instance of that class, with its own unique data.
Think about dogs. The concept of "Dog" is like a class — it says every dog has a name, a breed, and an age. Your specific dog "Buddy" is an object — a golden retriever who is 3 years old.
The type() function reveals the class behind every value. Now let's learn how to build our own classes.
How Do You Create Your First Class?
You create a class using the class keyword, followed by a name (by convention, starting with a capital letter), and a colon. The simplest class is just the keyword and pass:
Calling Dog() creates a brand-new Dog object. Each call makes a separate, independent object — just like each press of the cookie cutter makes a separate cookie.
What Is the __init__ Method?
A Dog class without any data isn't very useful. We need a way to give each dog its own name, breed, and age when we create it. That's exactly what __init__ does.
__init__ is a special method (called a constructor) that runs automatically every time you create a new object. It sets up the object's initial data.
Let's break this down piece by piece:
name argument as an attribute on the object. Now buddy.name will return 'Buddy'.How Do Instance Methods Work?
Attributes store data. Methods define behavior — they're functions that live inside a class and can access the object's data through self.
Notice two things. First, bark() reads self.name to personalize the message. Second, birthday() actually changes self.age — methods can modify the object's data, not just read it.
You call methods using dot notation: buddy.bark(). Python automatically passes buddy as self, so the method knows which dog is barking.
dog1_name = 'Buddy'
dog1_age = 3
def bark(name):
return f'{name} says: Woof!'
print(bark(dog1_name))class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
return f'{self.name} says: Woof!'
buddy = Dog('Buddy', 3)
print(buddy.bark())Classes bundle related data and behavior together. When your program has 50 dogs, the class approach scales cleanly while the scattered-variable approach becomes chaos.
What Is the Difference Between Class and Instance Attributes?
So far, every attribute we created was an instance attribute — data unique to each object. But sometimes you want data that's shared across all objects of a class. That's a class attribute.
Class attributes are defined directly inside the class body, outside any method. Instance attributes are defined inside __init__ using self..
How Do __str__ and __repr__ Control Display?
If you try to print an object without defining __str__, you get an ugly, unhelpful message like <__main__.Dog object at 0x7f...>. Let's fix that.
The __str__ method lets you define a human-friendly string for your object. Python calls it whenever you use print() or str() on the object.
The difference is audience. __str__ is for end users — it should be readable and friendly. __repr__ is for developers — it should look like valid Python code that could recreate the object.
How Does a Complete Class Look?
Let's build a BankAccount class that combines everything you've learned: __init__, instance methods, class attributes, and __str__.
This is the power of classes. The BankAccount class packages all the data (owner, balance) and all the rules (can't withdraw more than you have) into one clean, reusable unit.
Practice Exercises
Create a Book class with an __init__ method that takes title, author, and pages as parameters and stores them as instance attributes. Then create a book with title '1984', author 'George Orwell', and pages 328. Print each attribute on its own line.
Create a Rectangle class with width and height attributes. Add an area() method that returns the area (width times height). Create a rectangle with width 5 and height 3, then print the result of calling area().
Read the code carefully and predict what it prints. Think about what __init__ sets and how increment() changes self.count.
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
c = Counter()
print(c.increment())
print(c.increment())
print(c.increment())This Student class has two bugs. Fix them so the code prints:
Alice: 85.0Create a Product class with name and price attributes. Add a __str__ method that returns the format 'Product: <name> - $<price>'. Create a product with name 'Laptop' and price 999.99, then print it.
Create a Player class with:
total_players starting at 0__init__ takes name and sets self.score = 0, and increments Player.total_playerswin_round(self, points) method that adds points to self.scoreCreate two players 'Alice' and 'Bob'. Alice wins a round with 10 points, then Bob wins with 15 points. Print each player's score and the total number of players in this exact format:
Alice: 10
Bob: 15
Total players: 2Refactor this code into a ShoppingCart class. The class should have:
__init__ that creates an empty items listadd_item(self, name, price) that appends a tuple (name, price) to itemstotal(self) that returns the sum of all prices__str__ that returns 'Cart: <N> items, total $<total>'Then create a cart, add 'Apple' for 1.50 and 'Bread' for 3.00, and print the cart.
# Original code to refactor:
cart_items = []
def add_item(cart, name, price):
cart.append((name, price))
def get_total(cart):
return sum(price for _, price in cart)
add_item(cart_items, 'Apple', 1.50)
add_item(cart_items, 'Bread', 3.00)
print(f'Cart: {len(cart_items)} items, total ${get_total(cart_items)}')