Object-Oriented Programming (OOP) In Python

Table of contents

Object-Oriented Programming (OOP) is a powerful paradigm that makes code more modular, organized, and scalable. By focusing on the creation and interaction of "objects", OOP allows developers to design complex systems through smaller, reusable components. These components are easier to understand and maintain, which is essential when building large applications.
In this article, we’ll explore the key concepts of OOP in Python, including classes, objects, inheritance, polymorphism, and more... By understanding these principles, you can write cleaner and more flexible code.
This article might be a bit longer than usual, but I encourage you to read through it as it will provide you with a thorough understanding of OOP. Understanding these principles will help you write more efficient and maintainable code. So, let’s dive in!
What is OOP?
Object-Oriented Programming (OOP) is a programming style that organizes a program by bundling related properties and behaviors into individual objects. Each object represents a specific component of a larger system.
OOP allows you to define classes, which are blueprints for creating objects. These objects contain both data (attributes) and behavior (methods). For instance, a Car
class might define attributes like color
and speed
, and methods like accelerate()
or brake()
. Using OOP, you can build more maintainable and reusable applications, making your code scalable and organized.
Classes and Objects
A class is a template for creating objects. An object is an instance of a class that contains specific attributes and behaviors. By organizing data and methods together, classes help make your code reusable and scalable.
Concepts of Classes and Objects
Attributes: Variables that store data about an object. They can be instance-level (specific to an object) or class-level (shared by all objects of a class).
Methods: Functions that define an object's behavior. A special method,
__init__()
, initializes an object's attributes when it's created.Objects: Instances of a class with unique attributes and behaviors.
Example: Creating a Class and Object
class Parrot:
species = "Bird" # Class-level attribute
def __init__(self, name, age):
self.name = name # Instance attribute
self.age = age
def speak(self):
return f"{self.name} is {self.age} \
years old and belongs to the \
{self.species} species."
# Create instances of the Parrot class
parrot1 = Parrot("Blu", 10)
parrot2 = Parrot("Woo", 15)
# Access attributes and methods
print(parrot1.speak())
# Output: Blu is 10 years old and belongs to the Bird species.
print(parrot2.speak())
# Output: Woo is 15 years old and belongs to the Bird species.
Special Methods in Python
Python provides special methods (also called magic methods) that enhance the functionality of a class. The most commonly used special method is __init__()
, which initializes object attributes. Another example is __repr__()
, which defines how an object is represented as a string.
Example of __repr__()
Method:
class Electronic:
def __init__(self, name, brand, price):
self.name = name
self.brand = brand
self.price = price
def __repr__(self):
return f"Electronic(name='{self.name}', \
brand='{self.brand}', price={self.price})"
item = Electronic("Laptop", "Dell", 1200)
print(item)
# Output: Electronic(name='Laptop', brand='Dell', price=1200)
Understanding the self
Parameter
In OOP, self
refers to the instance of the class. It's used within class methods to access the object's attributes and other methods. When calling an object's method, self
is automatically passed, allowing you to refer to the current object.
Key OOP Principles
There are four main principles in OOP: Encapsulation, Inheritance, Polymorphism, and Abstraction. Let’s break them down with examples.
1. Encapsulation: Hiding Data and Protecting Integrity
Encapsulation is one of the core principles of Object-Oriented Programming (OOP). It refers to bundling the data (attributes) and methods (functions) that operate on the data into a single unit (class) while restricting direct access to some of the object's components. This concept ensures that the internal representation of an object is hidden from the outside, exposing only what is necessary for use.
Encapsulation is often implemented using access modifiers like private
, protected
, and public
. These determine how the attributes and methods of a class can be accessed or modified.
Features of Encapsulation
Data Hiding: Restrict access to certain parts of an object to ensure sensitive data is not directly modified.
Controlled Access: Use getter and setter methods to control how the data is accessed or modified.
Improved Security: Protect the integrity of the data by ensuring that only valid values are assigned.
Modularity: Keep the internal implementation of a class separate from its external behavior, promoting a modular design.
Here’s a simple example to illustrate encapsulation:
class BankAccount:
def __init__(self, account_holder, initial_balance):
self.account_holder = account_holder
self.__balance = initial_balance # Private attribute
# Getter method to access the private balance
def get_balance(self):
return self.__balance
# Setter method to modify the private balance
def deposit(self, amount):
if amount > 0:
self.__balance += amount
else:
print("Deposit amount must be positive!")
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
else:
print("Invalid withdrawal amount!")
# Usage
account = BankAccount("Alice", 1000)
print(account.get_balance())
# Output: 1000
account.deposit(500)
print(account.get_balance())
# Output: 1500
account.withdraw(2000)
# Output: Invalid withdrawal amount!
print(account.get_balance())
# Output: 1500
Explanation of the Example
Private Attribute:
- The
__balance
attribute is marked as private using double underscores (__
), making it inaccessible directly outside the class.
- The
Getter Method:
get_balance()
allows controlled access to the private attribute.
Setter Methods:
deposit()
andwithdraw()
modify the private balance while enforcing rules (e.g., no negative deposits, no over-withdrawals).
Advantages of Encapsulation
Improved Code Maintenance:
- Changes to the internal implementation do not affect external code.
Data Integrity:
- Protects sensitive data by ensuring only valid operations are performed.
Modularity:
- Keeps the codebase clean by separating an object's implementation from its interface.
Reusability:
- Encapsulation makes objects easier to reuse without exposing unnecessary details.
2. Inheritance: Reusing Code
Inheritance is a fundamental principle of Object-Oriented Programming (OOP) that allows a class (called the child class or subclass) to inherit attributes and methods from another class (called the parent class or base class). This enables code reuse, logical hierarchy, and extensibility, as you can build new classes based on existing ones, without rewriting common functionality.
Key Concepts of Inheritance
Parent Class: The class being inherited from, also called the base or superclass.
Child Class: The class that inherits from the parent class, also called the derived or subclass.
Method Overriding: The child class can redefine methods from the parent class to provide specific behavior.
Example: A simple example where child classes inherit from a single parent class.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound."
# Subclass: Dog
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof! Woof!"
# Subclass: Cat
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
# Usage
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak())
# Output: Buddy says Woof! Woof!
print(cat.speak())
# Output: Whiskers says Meow!
Explanation of the Example
Parent Class (
Animal
):- Contains the common logic for all animals, such as a name attribute and the
speak
method.
- Contains the common logic for all animals, such as a name attribute and the
Child Classes (
Dog
,Cat
):- Inherit the properties of
Animal
but override thespeak
method to provide specific sounds.
- Inherit the properties of
Code Reusability:
- Attributes
name
are inherited from the parent class, avoiding redundancy.
- Attributes
Advantages of Inheritance
Code Reusability: Common attributes and methods are defined once in the parent class and reused in child classes.
Logical Hierarchy: Establishes a clear relationship between parent and child classes (e.g., a dog is also an animal).
Extensibility: New functionality can be added by creating new child classes without altering the parent class.
3. Polymorphism: One Action, Many Forms
Polymorphism is a core principle of Object-Oriented Programming (OOP) that allows methods or functions to take on different forms depending on the object they are working with. The term "poly" means many, and "morph" means forms, so polymorphism literally means "many forms."
It enables a single interface to be used for different data types or classes, allowing objects of different types to be treated as if they belong to the same type. This makes code more flexible and easier to maintain.
Example: Here’s an example that demonstrates polymorphism by modeling animals and their unique sounds. Each animal has the same method speak
, but its implementation differs depending on the type of animal.
class Animal:
def speak(self):
return "I make some sound!"
# Subclass: Dog
class Dog(Animal):
def speak(self):
return "Woof! Woof!"
# Subclass: Cat
class Cat(Animal):
def speak(self):
return "Meow!"
# Subclass: Cow
class Cow(Animal):
def speak(self):
return "Moo!"
# Function demonstrating polymorphism
def animal_sound(animal):
print(animal.speak())
# Usage
dog = Dog()
cat = Cat()
cow = Cow()
animal_sound(dog) # Output: Woof! Woof!
animal_sound(cat) # Output: Meow!
animal_sound(cow) # Output: Moo!
Explanation of the Example
Parent Class (
Animal
)Defines a general method
speak
, which can be overridden by subclasses.The
speak
method in the parent class serves as a default behavior.
Subclasses (
Dog
,Cat
,Cow
)Each subclass provides its own implementation of the
speak
method.This demonstrates method overriding, a key aspect of polymorphism.
Polymorphic Function (
animal_sound
)Accepts any object of type
Animal
and calls itsspeak
method.The method that gets executed depends on the actual object passed to the function.
Usage
The
animal_sound
function is used with objects ofDog
,Cat
, andCow
.Each object calls its specific implementation of the
speak
method, showcasing polymorphism.
Benefits of Polymorphism
Code Reusability: Functions like
animal_sound
can handle a wide range of objects without modification.Extensibility: Adding a new animal (e.g.,
Bird
) requires only defining a new class and overriding thespeak
method—no changes are needed to theanimal_sound
function.Readability and Maintenance: Code becomes easier to read and maintain as behaviors are encapsulated within each class.
4. Abstraction: Hiding Complexity
Abstraction is one of the foundational principles of Object-Oriented Programming (OOP). It focuses on hiding the complexity of a system and exposing only the necessary and relevant details to the user. Think of it as driving a car—you don't need to know how the engine works internally to start and drive it. You simply interact with the steering wheel, accelerator, and brakes.
In Python, abstraction is achieved through abstract classes and interfaces. Abstract classes serve as blueprints, defining methods that must be implemented in any concrete subclass. This ensures consistency while allowing flexibility for different implementations.
Example: Here’s an example that uses the concept of abstraction to model different types of vehicles. The abstract class Vehicle
defines a common interface for all vehicles, but each type of vehicle implements its functionality differently.
from abc import ABC, abstractmethod
# Abstract class
class Vehicle(ABC):
@abstractmethod
def start(self):
"""Start the vehicle"""
pass
@abstractmethod
def stop(self):
"""Stop the vehicle"""
pass
# Subclass: Car
class Car(Vehicle):
def __init__(self, brand):
self.brand = brand
def start(self):
return f"The car {self.brand} starts with a key ignition."
def stop(self):
return f"The car {self.brand} stops using hydraulic brakes."
# Subclass: Bicycle
class Bicycle(Vehicle):
def __init__(self, brand):
self.brand = brand
def start(self):
return f"The bicycle {self.brand} starts when you pedal."
def stop(self):
return f"The bicycle {self.brand} stops \
when you press the hand brakes."
# Usage
car = Car("Toyota")
bicycle = Bicycle("Giant")
print(car.start())
# Output: The car Toyota starts with a key ignition.
print(car.stop())
# Output: The car Toyota stops using hydraulic brakes.
print(bicycle.start())
# Output: The bicycle Giant starts when you pedal.
print(bicycle.stop())
# Output: The bicycle Giant stops when you press the hand brakes.
Explanation of the Example
Abstract Class (
Vehicle
)Acts as a blueprint, ensuring that every subclass implements the
start
andstop
methods.Provides a consistent interface for all vehicles while allowing subclasses to define their specific behaviors.
Concrete Classes (
Car
andBicycle
)Implement the abstract methods defined in
Vehicle
.Car
has a start method that uses a key ignition, whileBicycle
starts by pedaling. Similarly, their stop methods are also tailored to their specific mechanisms.
Usage
The
Car
andBicycle
objects are created and their methods are called.Each type of vehicle behaves according to its implementation, demonstrating abstraction in action.
Benefits of Abstraction
Hides Complexity: Users only interact with the high-level methods (
start
,stop
) without worrying about how they are implemented.Encourages Consistency: All subclasses must follow the same interface, ensuring predictability in code.
Enhances Flexibility: New vehicle types (e.g.,
Motorbike
) can be added easily by extending theVehicle
class and implementing its abstract methods.Promotes Code Reusability: The abstract class can be reused across various subclasses, reducing redundancy.
Conclusion
In this article, we've covered the fundamental principles of Object-Oriented Programming (OOP) in Python. Let's recap the key concepts:
Classes and Objects – The building blocks of OOP that allow us to bundle data and methods into manageable components.
Encapsulation – Protecting the integrity of an object's data by restricting access and providing controlled methods for interaction.
Inheritance – Reusing and extending functionality from parent classes to child classes, promoting code reuse.
Polymorphism – Enabling one method to behave differently based on the object it operates on, allowing for more flexible code.
Abstraction – Hiding unnecessary complexity from the user, providing only the essential functionality for interacting with objects.
These OOP principles are essential for writing maintainable, scalable, and reusable Python code. As you continue to explore Python and software development, understanding and applying these concepts will allow you to design and build more robust and organized systems.
Subscribe to my newsletter
Read articles from Mostafijur Rahman directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Mostafijur Rahman
Mostafijur Rahman
Hi, I’m Mostafijur Rahman, a passionate full-stack software engineer with over 4 years of experience specializing in Django, React, and Next.js. I love solving real-world problems through code and building scalable, efficient web applications. I’m also enthusiastic about sharing my knowledge with the tech community, whether through coding tutorials, tech tips, or insights into modern web development. I'm not an expert or a guru; I'm just someone who loves learning and sharing my journey. My goal is to explore, grow, and help others who are on the same path. Whether it's coding, problem-solving, or diving into new topics, I’m here to share what I know and learn from others along the way. Let’s grow together!