At its core, Object-Oriented Programming (OOP) is a programming paradigm that organizes software design around data, or "objects," rather than functions and logic.
Think of it as modeling real-world things in code. Instead of writing a long list of instructions for the computer to follow linearly, you create independent building blocks (objects) that interact with each other.
A class is a template or blueprint for creating objects. It defines what attributes (data) and behaviors (functions/methods) the resulting objects will have, but it doesn't contain actual data itself.
An object is a concrete instance created from a class blueprint. It holds actual values and can execute the behaviors defined by its class.
Real-World Analogy: > * Class: The architectural blueprint for a car. It states that a car needs 4 wheels, a color, and an engine, and that it can "drive" and "brake."
Object: The actual physical car parked in your driveway. It has a specific color (Red) and a specific engine type (V8).
The structure, or building blocks, of object-oriented programming include the following:
Classes are user-defined data types that act as the blueprint for individual objects, attributes and methods.
Objects are instances of a class created with specifically defined data. Objects can correspond to real-world objects or an abstract entity. When class is defined initially, the description is the only object that is defined.
Methods are functions that are defined inside a class that describe the behaviors of an object. Each method contained in class definitions starts with a reference to an instance object. Additionally, the subroutines contained in an object are called instance methods. Programmers use methods for reusability or keeping functionality encapsulated inside one object at a time.
Attributes are defined in the class template and represent the state of an object. Objects will have data stored in the attributes field. Class attributes belong to the class itself.
To truly master OOP, you need to understand its four foundational principles.
Encapsulation is the practice of bundling data (variables) and the methods that act on that data into a single unit (a class), while restricting direct access to some of the object's components.
Why it matters: It prevents accidental data corruption from outside code. You use "getters" and "setters" to safely read or change the data.
Analogy: You press the power button on your phone to turn it on. The complex circuitry inside is hidden and protected from you messing with it directly.
Abstraction means hiding complex implementation details and only showing the essential features of an object to the user.
Why it matters: It reduces complexity. You don't need to know how something works internally to use it.
Analogy: When you drive a car, you use the steering wheel and gas pedal. You don't need to understand the internal combustion engine or fuel injection timing to make the car move.
Inheritance allows a new class (Child/Subclass) to adopt the attributes and methods of an existing class (Parent/Superclass).
Why it matters: It eliminates redundant code. You can build a general class and then create more specific versions of it without starting from scratch.
Analogy: A Vehicle parent class might have properties like speed and fuel. A Motorcycle child class inherits those properties but can also add unique features like hasSidecar.
Polymorphism allows different classes to be treated as instances of the same parent class, but respond to the exact same method call in their own unique way.
Why it matters: It allows for flexibility and scalability in your code.
Analogy: Imagine a parent class called Animal with a method called makeSound(). If you call makeSound() on a Dog object, it barks. If you call it on a Cat object, it meows. The same command yields different results depending on the object.
Languages generally fall into two categories: Pure OOP (everything must be an object) and Multi-paradigm (supports OOP alongside functional or procedural programming).
Java
Type: Multi-paradigm (Strict)
Primary Use Case: Enterprise software, Android apps, backend systems.
Quick Context: One of the most famous OOP languages. Almost everything (except primitive types) lives inside a class.
C++
Type: Multi-paradigm
Primary Use Case: Game engines, embedded systems, operating systems, high-performance apps.
Quick Context: Adds OOP features (classes, inheritance) on top of the procedural C language, giving you massive control over memory.
Python
Type: Multi-paradigm
Primary Use Case: AI/Machine Learning, data science, web development, scripting.
Quick Context: In Python, everything is an object (even strings and functions). It's incredibly flexible and beginner-friendly.
C#
Type: Multi-paradigm
Primary Use Case: Game development (Unity), Windows desktop apps, enterprise web backends.
Quick Context: Developed by Microsoft, it is highly structured and shares a lot of conceptual DNA with Java.
JavaScript / TypeScript
Type: Multi-paradigm
Primary Use Case: Full-stack web development (Frontend & Node.js).
Quick Context: Uses prototype-based OOP rather than traditional class-based OOP, though the modern class syntax makes it look familiar.
Ruby
Type: Pure OOP
Primary Use Case: Web development (Ruby on Rails).
Quick Context: A pure OOP language where absolutely everything is an object, designed with a focus on simplicity and developer happiness.
Modularity. Encapsulation enables objects to be self-contained, making troubleshooting and collaborative development easier.
Reusability. Code can be reused through inheritance, meaning a team does not have to write the same code multiple times.
Productivity. Programmers can construct new programs quicker through the use of multiple libraries and reusable code.
Easily upgradable and scalable. Programmers can implement system functionalities independently.
Interface descriptions. Descriptions of external systems are simple, due to message passing techniques that are used for objects communication.
Security. Using encapsulation and abstraction, complex code is hidden, software maintenance is easier and internet protocols are protected.
Flexibility. Polymorphism enables a single function to adapt to the class it is placed in. Different objects can also pass through the same interface.
In Python, a class is a blueprint or template for creating objects. Python is a multi-paradigm language, meaning it supports Object-Oriented Programming (OOP) fully—in fact, almost everything in Python is an object, including strings, lists, and integers.
Here is a complete breakdown of how classes work in Python, from basic syntax to core concepts.
To define a class, use the class keyword. By convention, Python class names use CamelCase.
class Car:
# A simple attribute
wheels = 4
# Creating an object (instantiating the class)
my_car = Car()
print(my_car.wheels) # Output: 4
To make classes useful, you need to allow objects to have unique data and specific behaviors. This is done using the __init__ method and self.
The __init__ method is a special function that runs automatically whenever you create a new object from a class. It is used to initialize the object's attributes.
The self parameter represents the specific object you are currently creating or manipulating. It allows Python to know which object's data to access. You must include it as the first parameter in your class methods, but you don't pass it manually when calling them.
class Dog:
# The Constructor
def __init__(self, name, breed):
self.name = name # Instance variable unique to each object
self.breed = breed # Instance variable
# A Class Method (Behavior)
def bark(self):
return f"{self.name} says Woof!"
# Creating distinct objects with unique data
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Rex", "German Shepherd")
# Accessing attributes and methods
print(dog1.name) # Output: Buddy
print(dog2.bark()) # Output: Rex says Woof!
It's important to know the difference between variables that belong to a single object and variables shared by all objects of that class.
Instance Variables: Defined inside __init__. They are unique to each individual object (e.g., self.name).
Class Variables: Defined directly inside the class but outside any methods. They are shared by every single instance of that class.
class Shark:
animal_type = "Fish" # Class variable (Shared by all sharks)
def __init__(self, name):
self.name = name # Instance variable (Unique to this shark)
sammy = Shark("Sammy")
stevie = Shark("Stevie")
print(sammy.animal_type) # Output: Fish
print(stevie.name) # Output: Stevie
Delete Object Properties
You can delete properties on objects by using the del keyword.
Delete Objects
You can delete objects by using the del keyword:
Example:
class products:
p_id=""
p_name=""
p=products()
p.p_id=1
p.p_name="Apple"
del p.p_name
print(p.p_id,p.p_name)
del p
print(p.p_id)
Python makes it incredibly easy for a new class to inherit attributes and methods from an existing class. You pass the parent class into the parentheses of the child class.
# Parent Class
class Vehicle:
def __init__(self, brand):
self.brand = brand
def start_engine(self):
return "Vroom!"
# Child Class inherits from Vehicle
class ElectricCar(Vehicle):
def charge_battery(self):
return "Charging..."
# Testing Inheritance
tesla = ElectricCar("Tesla")
print(tesla.brand) # Inherited attribute -> Output: Tesla
print(tesla.start_engine()) # Inherited method -> Output: Vroom!
print(tesla.charge_battery()) # Child-specific method -> Output: Charging...
Python super()
The super() builtin returns a proxy object (temporary object of the superclass) that allows us to access methods of the base class.
Example:
# Parent Class
class Animal:
def __init__(self):
print("Animals")
def walks(self):
print("Animals walk")
# Child Class
class Cat(Animal):
def __init__(self):
print("Cat")
super().__init__()
def walks(self):
print("A Cat walks across the street")
super().walks()
d=Cat()
d.walks()
Unlike languages like C++ or Java, Python doesn't have strict private keywords. Instead, it uses a naming convention to signal that a variable should not be accessed directly from outside the class:
Protected (_variable): A single underscore is a gentle warning to other developers saying, "Please treat this as private."
Private (__variable): A double underscore activates Name Mangling, which makes it harder (though not entirely impossible) to access the variable from outside.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.__balance = balance # Private attribute
# Getter method to safely access the data
def get_balance(self):
return self.__balance
account = BankAccount("Alice", 5000)
# print(account.__balance) # Throws an AttributeError
print(account.get_balance()) # Safe and proper way -> Output: 5000
In Python, polymorphism manifests in three main ways: Duck Typing, Method Overriding, and Polymorphism in Built-in Functions/Operators.
Python relies heavily on a philosophy called Duck Typing. The phrase comes from the saying:
"If it walks like a duck and quacks like a duck, it’s a duck."
In strict languages, if a function expects an object of class Bird, you must pass a Bird (or its subclass). In Python, a function doesn't care about the type of the object; it only cares about whether the object has the specific methods or behaviors required at that moment.
Here is how duck typing allows completely unrelated classes to be treated interchangeably:
class Duck:
def fly(self):
print("Flap flap! The duck is flying.")
class Airplane:
def fly(self):
print("Engines roaring! The airplane takes off.")
class Whale:
def swim(self):
print("Splash! The whale swims.")
# A polymorphic function that accepts ANY object with a .fly() method
def lift_off(entity):
entity.fly() # It doesn't check the class type, only calls the method
# Testing Duck Typing
donald = Duck()
boeing = Airplane()
lift_off(donald) # Output: Flap flap! The duck is flying.
lift_off(boeing) # Output: Engines roaring! The airplane takes off.
# lift_off(Whale()) -> This would crash because Whale doesn't "quack" (fly)
This is the classic OOP approach. A child class inherits a method from a parent class but rewrites (overrides) the internal logic to fit its own specific needs.
Example: Create a class called Vehicle and make Car, Boat, Plane child classes of Vehicle
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def move(self):
print("Move!")
class Car(Vehicle):
pass
class Boat(Vehicle):
def move(self):
print("Sail!")
class Plane(Vehicle):
def move(self):
print("Fly!")
car1 = Car("Ford", "Mustang") #Create a Car object
boat1 = Boat("Ibiza", "Touring 20") #Create a Boat object
plane1 = Plane("Boeing", "747") #Create a Plane object
for x in (car1, boat1, plane1):
print(x.brand)
print(x.model)
x.move()
Child classes inherits the properties and methods from the parent class.
In the example above you can see that the Car class is empty, but it inherits brand, model, and move() from Vehicle.
The Boat and Plane classes also inherit brand, model, and move() from Vehicle, but they both override the move() method.
Because of polymorphism we can execute the same method for all classes.
You have actually been using polymorphism in Python since day one without realizing it. Python's built-in functions and operators are highly polymorphic.
The len() function can accept strings, lists, dictionaries, or tuples, and it calculates the length of each appropriately because it internally looks for a __len__() method on the object.
print(len("Python")) # Output: 6 (Counts characters)
print(len([1, 2, 3, 4])) # Output: 4 (Counts elements)
The plus operator changes its behavior entirely depending on the data types it is acting upon (Operator Overloading):
print(5 + 5) # Output: 10 (Arithmetic addition)
print("Hello " + "World") # Output: Hello World (String concatenation)
print([1, 2] + [3, 4]) # Output: [1, 2, 3, 4] (List merging)
In languages like C++ or Java, you can have two functions with the exact same name but different arguments (e.g., add(int x, int y) and add(int x, int y, int z)).
Python does not natively support traditional method overloading. If you write two methods with the same name, Python will simply overwrite the first one and only recognize the last one defined.
To achieve overloading-like behavior, Python developers use default arguments or variable-length arguments (*args, kwargs):
class Calculator:
# Simulating overloading using default parameters
def add(self, a, b, c=0):
return a + b + c
calc = Calculator()
print(calc.add(2, 3)) # Output: 5 (c defaults to 0)
print(calc.add(2, 3, 4)) # Output: 9