Preparing your learning space...
100% through Object-Oriented Programming tutorials
A practical guide to writing cleaner, reusable code with classes and objects. You'll learn the core concepts by building real examples.
Object-Oriented Programming is a way of writing code that groups related data and behavior into objects. Instead of passing data around separate functions, you bundle the data and the functions that work on it together.
Procedural approach — data and functions are separate:
name = "Alice"
balance = 100
def deposit(amount):
global balance
balance += amount
OOP approach — data and functions live together:
class BankAccount:
def __init__(self, name, balance):
self.name = name
self.balance = balance
def deposit(self, amount):
self.balance += amount
OOP helps you model real-world things (users, products, orders) as code, keeps related code organized, and makes reuse easier.
A class is a blueprint. An object is an actual thing built from that blueprint.
class Car:
pass
my_car = Car() # my_car is an object (instance) of the Car class
your_car = Car() # separate object, same blueprint
You can create as many objects from one class as you need. Each one is independent — changing my_car does not affect your_car.
Attributes store data about an object. Methods are functions that belong to an object.
class Dog:
def __init__(self, name, breed):
self.name = name # attribute
self.breed = breed # attribute
def bark(self): # method
return f"{self.name} says Woof!"
def describe(self): # method
return f"{self.name} is a {self.breed}"
dog1 = Dog("Max", "Golden Retriever")
print(dog1.name) # Max
print(dog1.bark()) # Max says Woof!
print(dog1.describe()) # Max is a Golden Retriever
Attributes hold the state. Methods define what the object can do.
The constructor (__init__) runs automatically when you create an object. It sets up the initial state of the object.
class Student:
def __init__(self, name, grade, student_id):
self.name = name
self.grade = grade
self.student_id = student_id
self.attendance = 0 # default value, not passed in
def mark_present(self):
self.attendance += 1
s1 = Student("Emma", "A", "STU001")
s2 = Student("Liam", "B", "STU002")
print(s1.name, s1.grade) # Emma A
print(s2.name, s2.grade) # Liam B
The self parameter refers to the current object instance. Every method needs self as its first parameter.
Encapsulation means keeping internal data private and only exposing what's necessary. In Python, prefixing an attribute with _ (protected) or __ (private) signals that it shouldn't be accessed directly.
Use getters and setters (or properties) to control access.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # private attribute
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
else:
print("Insufficient funds")
def get_balance(self): # controlled read access
return self.__balance
acc = BankAccount("Alice", 1000)
acc.deposit(500)
acc.withdraw(200)
print(acc.get_balance()) # 1300
# print(acc.__balance) # AttributeError — private
Why encapsulate? It prevents external code from putting your object into an invalid state (e.g., a negative bank balance).
Inheritance lets one class reuse the attributes and methods of another class. The child class gets everything from the parent class and can add or override what it needs.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
def move(self):
return f"{self.name} moves"
class Cat(Animal): # Cat inherits from Animal
def speak(self): # override the parent method
return f"{self.name} says Meow!"
class Bird(Animal):
def __init__(self, name, wingspan):
super().__init__(name) # call parent constructor
self.wingspan = wingspan
def speak(self):
return f"{self.name} says Chirp!"
def fly(self): # new method, not in parent
return f"{self.name} flies with {self.wingspan}cm wings"
cat = Cat("Whiskers")
bird = Bird("Tweety", 25)
print(cat.speak()) # Whiskers says Meow!
print(cat.move()) # Whiskers moves (inherited)
print(bird.speak()) # Tweety says Chirp!
print(bird.fly()) # Tweety flies with 25cm wings
Use super() to call the parent class's methods. This avoids duplicating parent initialization code.
Polymorphism means different classes can use the same method name but behave differently. The calling code doesn't need to know which specific class it's working with.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
class Triangle:
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
# Polymorphic function — works with any shape that has .area()
def print_area(shapes):
for shape in shapes:
print(f"Area: {shape.area()}")
shapes = [
Rectangle(5, 10),
Circle(7),
Triangle(4, 6)
]
print_area(shapes)
# Area: 50
# Area: 153.938...
# Area: 12.0
The print_area function doesn't care what type each shape is. It only cares that the object has an area() method. This lets you add new shape types without changing existing code.
Abstraction means hiding complex implementation details and only showing the essential features. In Python, you can use the abc module to create abstract base classes that define what methods a child class must implement.
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def pay(self, amount):
"""Process a payment — must be implemented by subclasses"""
pass
def receipt(self, amount): # concrete method, shared by all
return f"Payment of ${amount:.2f} completed"
class CreditCard(PaymentProcessor):
def pay(self, amount):
return f"Charging ${amount:.2f} to credit card"
class PayPal(PaymentProcessor):
def pay(self, amount):
return f"Redirecting to PayPal for ${amount:.2f}"
class Crypto(PaymentProcessor):
def pay(self, amount):
return f"Sending {amount * 0.000042} BTC"
# payment = PaymentProcessor() # TypeError — can't instantiate abstract class
cards = [CreditCard(), PayPal(), Crypto()]
for card in cards:
print(card.pay(50.00))
print(card.receipt(50.00))
The abstract class guarantees every payment method has a pay() function. You can add new payment types later without changing the code that uses them.
OOP is a tool, not a rule. Use classes when you're modeling things with related data and behavior, or when code reuse across similar types makes sense. For simple scripts, functions are often enough.
Save your progress and earn XP for completing tutorials.
Keep learning
Technology
Python
Lesson group
Object-Oriented Programming
Progress
100% complete