Sitemap
hassane Skikri

hassane Skikri

A technology enthusiast who likes writing about different technologies including Python, Data Science, Machine learning, data analyse, etc.

Lesson 5: Object-Oriented Programming (OOP) in Python

4 min readJul 20, 2026

--

Press enter or click to view image in full size
Photo by Mahesh Ranaweera on Unsplash

Object-Oriented Programming (OOP) bundles data and its operations into reusable ‘objects’ to make software modular and maintainable.

The Four Pillars of OOP

OOP is built on four fundamental principles:

  1. Encapsulation: The bundling of data and the methods that operate on that data into a single unit (a class). It restricts direct access to some of an object’s components, which is a key principle of data hiding.
  2. Inheritance: A mechanism that allows a new class (child/subclass) to inherit attributes and methods from an existing class (parent/superclass). This promotes code reuse.
  3. Polymorphism: The ability of different objects to respond to the same message (method call) in their own unique ways. This allows for flexibility and dynamic behavior.
  4. Abstraction: The concept of hiding the complex implementation details and showing only the essential features of the object. This simplifies the use of objects.

1. Classes and Objects: The Blueprint and the House

  • Class: The blueprint or template for creating objects. It defines a set of attributes and methods that the created objects will have.
  • Object (Instance): A specific instance of a class. It is a concrete realization of the blueprint, with actual data values for its attributes.
Press enter or click to view image in full size

The __init__ Method (The Constructor)

The __init__ method is a special method that is automatically called when you create a new object from a class. It's used to initialize the object's attributes.

The self Keyword

The self keyword represents the instance of the class. By using self, we can access the attributes and methods of the class in python. It binds the attributes with the given arguments.

Example:

# Define the Car class (blueprint)
class Car:
# The constructor method to initialize a new Car object
def __init__(self, make, model, year):
# Attributes (data)
self.make = make
self.model = model
self.year = year
self.is_running = False

# A method (action)
def start_engine(self):
"""Starts the car's engine."""
self.is_running = True
return f"The {self.make} {self.model}'s engine is now running."

# Another method
def stop_engine(self):
"""Stops the car's engine."""
self.is_running = False
return f"The {self.make} {self.model}'s engine has been stopped."

# Create objects (instances) of the Car class
car1 = Car("Toyota", "Camry", 2022)
car2 = Car("Honda", "Civic", 2023)

# Access attributes and call methods
print(f"Car 1: {car1.year} {car1.make} {car1.model}")
print(car1.start_engine()) # Output: The Toyota Camry's engine is now running.
print(f"Is Car 1 running? {car1.is_running}") # Output: Is Car 1 running? True

2. Inheritance: Building on Existing Classes

Inheritance allows us to define a class that inherits all the methods and properties from another class.

  • Parent class (Superclass): The class being inherited from.
  • Child class (Subclass): The class that inherits from another class.

Example:

# Parent class
class Animal:
def __init__(self, name):
self.name = name

def speak(self):
raise NotImplementedError("Subclass must implement abstract method")

# Child classes
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"

class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"

# Create instances of the child classes
my_dog = Dog("Buddy")
my_cat = Cat("Whiskers")

print(my_dog.speak()) # Output: Buddy says Woof!
print(my_cat.speak()) # Output: Whiskers says Meow!

3. Polymorphism: One Interface, Many Forms

Polymorphism allows us to use a single interface (like a method call) to represent different underlying forms (data types or classes). This is often used with inheritance.

Example:

# Using the Animal classes from the previous example

def make_animal_speak(animal):
"""
This function doesn't need to know if the animal is a Dog or a Cat.
It just calls the .speak() method.
"""
print(animal.speak())

# Create a list of different animal objects
animals = [Dog("Rex"), Cat("Fluffy"), Dog("Max")]

# The polymorphic loop
for animal in animals:
make_animal_speak(animal)
# Output:
# Rex says Woof!
# Fluffy says Meow!
# Max says Woof!

4. Abstraction: Hiding Complexity

Abstraction means hiding the complex implementation details and showing only the essential features of the object. In Python, this can be achieved by using abstract classes and methods.

Get hassane Skikri’s stories in your inbox

Join Medium for free to get updates from this writer.

An abstract method is a method that is declared, but contains no implementation. Abstract classes cannot be instantiated.

Example:

from abc import ABC, abstractmethod

# Abstract parent class
class Shape(ABC):
@abstractmethod
def area(self):
"""Abstract method for calculating the area of a shape."""
pass

# Concrete child classes
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height

class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius

# Create instances of the concrete classes
rectangle = Rectangle(10, 5)
circle = Circle(7)

print(f"Area of Rectangle: {rectangle.area()}") # Output: Area of Rectangle: 50
print(f"Area of Circle: {circle.area()}") # Output: Area of Circle: 153.86

# You cannot create an instance of an abstract class:
# shape = Shape() # This would raise a TypeError

5. Encapsulation

Restricting direct access to data and methods in a class.

class BankAccount:  
def __init__(self):
self.__balance = 0 # Private variable: Cannot be accessed directly outside the class.
self._type = "Savings" # Protected variable: Can be accessed in subclasses.

# Getter method: Provides controlled access to the private variable.
@property
def balance(self):
return self.__balance

# Setter method: Allows modifying the private variable with validation.
@balance.setter
def balance(self, amount):
if amount >= 0:
self.__balance = amount
else:
raise ValueError("Balance cannot be negative")

account = BankAccount()
account.balance = 2000
print(account.balance) # Output: 2000
print(account._type) # Output: Savings

--

--

Written by hassane Skikri

A technology enthusiast who likes writing about different technologies including Python, Data Science, Machine learning, data analyse, etc.