Great!
Let’s break down the OOPs (Object-Oriented Programming)
concepts in Python in a simple and detailed way — perfect for
understanding, interview prep, or adding to your notes/project report.
OOPs Concepts in Python
Object-Oriented Programming (OOP) is a programming paradigm based
on the concept of "objects", which can contain data (attributes) and
functions (methods). Python supports OOP fully.
Main OOP Concepts in Python:
Concept Description
Blueprint or template for creating objects. Defines
1. Class
attributes and methods.
2. Object Instance of a class. Represents real-world entities.
Allows one class to inherit the properties and methods
3. Inheritance
of another.
4. Hides internal data by restricting direct access using
Encapsulation private/protected members.
5. Ability to take many forms – same method name
Polymorphism behaving differently in different contexts.
Hides complex implementation and shows only the
6. Abstraction
essential features.
1. Class and Object
class Student:
def __init__(self, name, roll):
self.name = name
self.roll = roll
def show(self):
print(f"Name: {self.name}, Roll: {self.roll}")
# Object
s1 = Student("Shweta", 101)
s1.show()
2. Inheritance
class Animal:
def speak(self):
print("Animal speaks")
class Dog(Animal): # Inheriting Animal
def bark(self):
print("Dog barks")
d = Dog()
d.speak()
d.bark()
3. Encapsulation
class Account:
def __init__(self):
self.__balance = 0 # private attribute
def deposit(self, amount):
self.__balance += amount
def get_balance(self):
return self.__balance
a = Account()
a.deposit(1000)
print(a.get_balance())
4. Polymorphism
class Cat:
def sound(self):
print("Meow")
class Dog:
def sound(self):
print("Bark")
# Same method name but different behavior
for animal in (Cat(), Dog()):
animal.sound()
5. Abstraction (using ABC module)
from abc import ABC, abstractmethod
class Vehicle(ABC):
@abstractmethod
def start(self):
pass
class Car(Vehicle):
def start(self):
print("Car started")
c = Car()
c.start()
Why Use OOP in Python?
• Organizes code better
• Promotes reusability and scalability
• Mimics real-world structure
• Makes maintenance easier
Sure Shweta! Here’s a detailed explanation of “Classes in Python”,
perfect for understanding, interviews, and adding to your final project
report or notes.
Classes in Python (Detailed Explanation)
Definition:
A class in Python is a user-defined blueprint or prototype from which
objects are created. Classes encapsulate data (attributes) and functions
(methods) that operate on the data.
Key Concepts of a Class:
Term Description
class
Used to define a new class.
keyword
Special method (constructor) that runs when a new
__init__()
object is created.
self keyword Refers to the current instance of the class.
Attributes Variables that belong to the class.
Functions that belong to the class and define behaviors of
Methods
the objects.
Basic Syntax:
class ClassName:
def __init__(self, attribute1, attribute2):
self.attribute1 = attribute1
self.attribute2 = attribute2
def method1(self):
print("This is a method")
Example: Student Class
class Student:
def __init__(self, name, roll):
self.name = name # attribute
self.roll = roll
def display_info(self): # method
print(f"Name: {self.name}, Roll No: {self.roll}")
Creating Objects:
s1 = Student("Shweta", 101)
s2 = Student("Amit", 102)
s1.display_info()
s2.display_info()
Output:
Name: Shweta, Roll No: 101
Name: Amit, Roll No: 102
Why Use Classes?
• Organizes code into reusable structures
• Models real-world entities (like Student, Car, Employee)
• Enables OOP principles: inheritance, encapsulation, etc.
• Easier to manage large programs
Types of Methods in a Class:
Method Type Description
Instance
Works with instance (object) data. Most common type.
Method
Works with class-level data using @classmethod
Class Method
decorator.
Independent of class or instance. Uses @staticmethod
Static Method
decorator.
Example with All Method Types:
class Example:
class_variable = "Class Level"
def __init__(self, instance_var):
self.instance_var = instance_var
def instance_method(self):
print("Instance Variable:", self.instance_var)
@classmethod
def class_method(cls):
print("Class Variable:", cls.class_variable)
@staticmethod
def static_method():
print("Static method doesn’t access class or instance data")
Great question, Shweta! Let’s dive into Constructors in Python and
their types — explained clearly with examples, suitable for interviews,
notes, or project reports.
Constructors in Python
What is a Constructor?
A constructor is a special method in a class that is automatically called
when an object of the class is created.
In Python, the constructor method is always named: __init__().
Purpose:
• Initialize the object with default or user-defined values.
• Set up necessary variables or states at the time of object creation.
Syntax of Constructor:
class ClassName:
def __init__(self): # Constructor
# initialization code
Types of Constructors in Python
Python supports three types of constructors:
1⃣ Default Constructor
• No parameters except self
• Used when no initialization is needed or values are hardcoded.
class Demo:
def __init__(self): # Default constructor
print("This is a default constructor")
obj = Demo()
2⃣ Parameterized Constructor
• Takes arguments during object creation.
• Used to initialize object with specific values.
class Student:
def __init__(self, name, roll): # Parameterized constructor
self.name = name
self.roll = roll
def show(self):
print(f"Name: {self.name}, Roll: {self.roll}")
s = Student("Shweta", 101)
s.show()
3⃣ Constructor Overloading (Not directly supported)
Python does not support method overloading (same name, different
parameters) directly like Java or C++.
But we can simulate it using default arguments or *args.
class Example:
def __init__(self, name="Guest"): # Simulated constructor
overloading
self.name = name
def greet(self):
print(f"Hello, {self.name}")
e1 = Example()
e2 = Example("Shweta")
e1.greet() # Hello, Guest
e2.greet() # Hello, Shweta
Summary Table:
Type Description Example Use
Default Initializing values like
No parameters, basic setup
Constructor counters, lists
Takes parameters to Student name, roll
Parameterized
initialize object attributes number, etc.
Simulated Using default values or Flexible initialization
Overloading *args to mimic overload based on arguments
Absolutely Shweta! Here's a clear and detailed explanation of Data
Hiding in Python, ideal for your notes, interviews, or project report.
Data Hiding in Python
What is Data Hiding?
Data hiding is an Object-Oriented Programming (OOP) concept that
restricts access to internal object details — like variables or methods —
to prevent direct modification or misuse from outside the class.
It helps achieve encapsulation and increases security and control over
the data.
Why is Data Hiding Important?
• Protects sensitive data
• Prevents accidental changes
• Allows controlled access via methods (getters and setters)
How Data Hiding Works in Python
Python does not have access specifiers like private, public, or protected,
but it uses naming conventions:
Syntax
Type Access Level
Example
Public Variable self.name Accessible from anywhere
Protected Suggests internal use only (can be
self._name
Variable accessed)
Syntax
Type Access Level
Example
Not accessible directly from outside
Private Variable self.__name
the class
Example of Data Hiding:
class Student:
def __init__(self, name, marks):
self.__name = name # private attribute
self.__marks = marks # private attribute
def display(self):
print(f"Name: {self.__name}, Marks: {self.__marks}")
def update_marks(self, new_marks):
if new_marks > 0:
self.__marks = new_marks
else:
print("Invalid marks!")
s = Student("Shweta", 85)
s.display()
# Trying to access private data directly (will cause error)
# print(s.__marks)
# Correct way to update
s.update_marks(90)
s.display()
Accessing Private Variables (if needed):
Even though private variables can’t be accessed directly, you can still
access them using:
print(s._Student__marks) # Not recommended, but possible
This is called Name Mangling — Python changes __marks to
_ClassName__marks.
Best Practice:
Always use getter and setter methods to access or update private data.
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def get_balance(self):
return self.__balance
def set_balance(self, amount):
if amount >= 0:
self.__balance = amount
else:
print("Invalid balance!")
acc = BankAccount(5000)
print(acc.get_balance())
acc.set_balance(6000)
Summary Table:
Concept Keyword/Syntax Example
Public self.name Accessible everywhere
Protected self._name Use within class/subclass
Private self.__name Only within class, uses name mangling
Creating classes in Python is a key part of object-oriented programming
(OOP). A class is like a blueprint for creating objects (instances), which
bundle data and functionality together.
Here’s a basic example to show you how to create and use a class in
Python:
Basic Class Syntax
class Dog:
def __init__(self, name, age):
self.name = name # instance variable
self.age = age
def bark(self):
print(f"{self.name} says woof!")
def get_age(self):
return self.age
Creating and Using Objects
my_dog = Dog("Buddy", 3)
my_dog.bark() # Output: Buddy says woof!
print(my_dog.get_age()) # Output: 3
Explanation:
• class Dog: – Defines a class named Dog.
• __init__ – The constructor method, called automatically when a
new object is created.
• self – Refers to the current instance of the class.
• bark and get_age – Methods (functions defined inside a class).
Some Extra Notes:
• You can add more methods and attributes.
• Use self.attribute to access instance variables.
• Python doesn’t have explicit access modifiers like public or private
(though you can use _ or __ to indicate they shouldn’t be accessed
externally).
Great! Let's dive into instance methods in Python
What Are Instance Methods?
Instance methods are functions defined inside a class that operate on
an instance of that class. They have access to both the instance's data
(self) and other methods.
Syntax of an Instance Method:
class MyClass:
def instance_method(self):
# method body
pass
The first parameter is always self, which represents the instance calling
the method.
Example:
class Dog:
def __init__(self, name):
self.name = name # instance variable
def greet(self):
print(f"Hi, I'm {self.name}!")
Usage:
d = Dog("Max")
d.greet() # Output: Hi, I'm Max!
Key Points:
• self gives you access to the instance's attributes and other
methods.
• You must call the method on an instance (e.g., d.greet()), not on
the class unless you pass an instance manually.
• You can have multiple instance methods that interact with each
other.
Another Example:
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
print(f"${amount} deposited. New balance: ${self.balance}")
def withdraw(self, amount):
if amount > self.balance:
print("Insufficient funds.")
else:
self.balance -= amount
print(f"${amount} withdrawn. New balance: ${self.balance}")
acc = BankAccount("Alice", 1000)
acc.deposit(200) # $200 deposited. New balance: $1200
acc.withdraw(1500) # Insufficient funds.
Sure Shweta! Here's a complete explanation of Special Methods in
Python, also called Magic Methods or Dunder Methods — perfect for
interviews, notes, or your project report.
Special Methods in Python
What Are Special Methods?
Special methods are predefined methods in Python that have double
underscores at the beginning and end of their names.
They are also called Magic Methods or Dunder Methods (DUal
UNDERscore).
These methods customize the behavior of your objects in built-in
operations like printing, adding, comparing, etc.
Common Special Methods with Examples
Method Name Purpose / Use
__init__() Constructor – initializes object state
Method Name Purpose / Use
__str__() Returns a string representation of the object
__repr__() Returns an official string representation (for debugging)
__len__() Used by len() function
__add__() Used to define behavior for + operator
__eq__() Compares two objects with ==
__del__() Destructor – called when an object is deleted
1. __init__() – Constructor
class Student:
def __init__(self, name):
self.name = name
2. __str__() – For Print
class Student:
def __init__(self, name):
self.name = name
def __str__(self):
return f"Student name is {self.name}"
s = Student("Shweta")
print(s) # Automatically calls __str__()
3. __len__() – Customize len()
class MyList:
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
ml = MyList([1, 2, 3])
print(len(ml)) # Output: 3
4. __add__() – Operator Overloading
class Marks:
def __init__(self, score):
self.score = score
def __add__(self, other):
return self.score + other.score
m1 = Marks(40)
m2 = Marks(60)
print(m1 + m2) # Output: 100
5. __eq__() – Comparison with ==
class Person:
def __init__(self, age):
self.age = age
def __eq__(self, other):
return self.age == other.age
p1 = Person(21)
p2 = Person(21)
print(p1 == p2) # True
6. __del__() – Destructor
class Sample:
def __del__(self):
print("Object is being deleted")
obj = Sample()
del obj
Summary Table:
Method Triggered When You... Purpose
__init__() Create an object Initialize object
__str__() Print the object Return user-friendly string
__repr__() Use in debugging (e.g. IDE) Return official representation
__len__() Use len() Define object length
__add__() Use + Add two objects
__eq__() Use == Compare two objects
__del__() Delete an object Clean-up (optional)
Absolutely, Shweta! Here's a clear and detailed explanation of Class
Variables in Python, great for your notes, interviews, or your project
report.
Class Variables in Python
Definition:
A class variable is a variable that is shared by all objects (instances) of a
class.
It is defined inside the class, but outside any instance methods (like
__init__()).
Key Points:
Feature Description
All instances share the same class variable unless
Shared Value
specifically overridden.
Defined Declared directly inside the class but outside any
Location method.
Access Accessed using ClassName.variable_name or
Method self.variable_name
Syntax Example:
class Student:
school = "JEC College" # Class variable
def __init__(self, name):
self.name = name # Instance variable
s1 = Student("Shweta")
s2 = Student("Amit")
print(s1.school) # Output: JEC College
print(s2.school) # Output: JEC College
Modifying Class Variable:
• You should change it using ClassName.variable, not self.variable,
or it will create a new instance variable.
Student.school = "KV School"
print(s1.school) # KV School
print(s2.school) # KV School
Class Variable vs Instance Variable
Feature Class Variable Instance Variable
Belongs To Class Individual object
Shared Among No (each object has its
Yes
Objects own copy)
Outside all methods,
Defined Inside __init__() using self
inside class
Feature Class Variable Instance Variable
Accessed Via ClassName or object Only via object using self
Example With Both Variables:
class Employee:
company = "TCS" # Class variable
def __init__(self, name, salary):
self.name = name # Instance variable
self.salary = salary # Instance variable
emp1 = Employee("Shweta", 50000)
emp2 = Employee("Amit", 60000)
print(emp1.company) # TCS
print(emp2.company) # TCS
Employee.company = "Infosys" # Change class variable
print(emp1.company) # Infosys
print(emp2.company) # Infosys
Summary:
• Use class variables when a value should be common to all objects
(like school, company, etc.).
• Use instance variables for object-specific data (like name, marks,
etc.).
Absolutely Shweta! Here's a complete and clear explanation of
Inheritance and its Types in Python — perfect for your interview prep,
project report, or handwritten notes.
Inheritance in Python
Definition:
Inheritance is an Object-Oriented Programming (OOP) concept where
one class (called child or derived class) inherits the properties and
methods of another class (called parent or base class).
Purpose of Inheritance:
• Code reusability
• Extensibility
• Helps follow the DRY (Don't Repeat Yourself) principle
Basic Syntax:
class Parent:
def display(self):
print("This is Parent class")
class Child(Parent): # Inheriting from Parent
def show(self):
print("This is Child class")
c = Child()
c.display() # Inherited from Parent
c.show() # Own method
Types of Inheritance in Python
Type Description Example Classes
1. Single One child inherits from one parent A→B
One child inherits from multiple
2. Multiple A, B → C
parents
3. Multilevel Inheritance in a chain of classes A→B→C
4. Multiple children inherit from the
A → B, A → C
Hierarchical same parent
Type Description Example Classes
Combination of multiple inheritance (Mix of above
5. Hybrid
types types)
1. Single Inheritance
class Parent:
def parent_method(self):
print("Parent class")
class Child(Parent):
def child_method(self):
print("Child class")
c = Child()
c.parent_method()
2. Multiple Inheritance
class Father:
def skills(self):
print("Gardening")
class Mother:
def hobbies(self):
print("Cooking")
class Child(Father, Mother):
pass
c = Child()
c.skills()
c.hobbies()
3. Multilevel Inheritance
class Grandparent:
def gp_method(self):
print("Grandparent")
class Parent(Grandparent):
def p_method(self):
print("Parent")
class Child(Parent):
def c_method(self):
print("Child")
c = Child()
c.gp_method()
c.p_method()
4. Hierarchical Inheritance
class Parent:
def p_method(self):
print("Parent class")
class Child1(Parent):
def c1_method(self):
print("Child1")
class Child2(Parent):
def c2_method(self):
print("Child2")
c1 = Child1()
c2 = Child2()
c1.p_method()
c2.p_method()
5. Hybrid Inheritance
Hybrid inheritance is a mix of multiple and multilevel (or any
combination). Python uses Method Resolution Order (MRO) to manage
this.
class A:
def method_a(self):
print("Class A")
class B(A):
def method_b(self):
print("Class B")
class C:
def method_c(self):
print("Class C")
class D(B, C):
def method_d(self):
print("Class D")
obj = D()
obj.method_a()
obj.method_c()
Summary Table:
Inheritance Type Key Feature Example Classes
Single One parent, one child A→B
Multiple Multiple parents, one child A, B → C
Multilevel Chain of inheritance A→B→C
Hierarchical One parent, multiple children A → B, A → C
Hybrid Mixed types A → B, A → C → D
Nice one! Let’s talk about Type Identification in Python
What is Type Identification?
Type identification means figuring out (or checking) the data type of a
variable or value during runtime.
Common Tools for Type Identification:
1. type() – Built-in function to get the type of a variable
x = 42
print(type(x)) # <class 'int'>
y = "hello"
print(type(y)) # <class 'str'>
2. isinstance() – To check if an object is of a specific type
x = 3.14
print(isinstance(x, float)) # True
print(isinstance(x, int)) # False
You can check against multiple types at once:
print(isinstance(x, (int, float))) # True
3. __class__ attribute – Less common, but gives the type directly
x = [1, 2, 3]
print(x.__class__) # <class 'list'>
Type Identification with Custom Classes:
class Animal:
pass
class Dog(Animal):
pass
d = Dog()
print(type(d)) # <class '__main__.Dog'>
print(isinstance(d, Dog)) # True
print(isinstance(d, Animal)) # True (because Dog inherits from Animal)
type() vs isinstance():
Feature type() isinstance()
Exact match Yes No (allows subclasses)
Inheritance Ignores subclass Aware of inheritance
Use case Checking strict type Validating types with flexibility
Awesome choice! Let’s talk about Polymorphism in Python – a key
concept in object-oriented programming (OOP)
What is Polymorphism?
Polymorphism means "many forms". In Python, it refers to the ability
to use the same method name across different classes, with each class
having its own implementation.
In short: same interface, different behavior.
Basic Example:
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
def make_sound(animal):
print(animal.speak())
d = Dog()
c = Cat()
make_sound(d) # Output: Woof!
make_sound(c) # Output: Meow!
Even though Dog and Cat are different classes, both have a .speak()
method — that's polymorphism!
Real-World Analogy:
Think of a "drive" method:
• A Car drives on roads.
• A Boat drives on water.
• A Plane drives in the air (kind of ).
But they all respond to .drive() — differently!
Polymorphism with Inheritance:
class Animal:
def speak(self):
return "Some sound"
class Dog(Animal):
def speak(self):
return "Bark"
class Cat(Animal):
def speak(self):
return "Purr"
animals = [Dog(), Cat(), Animal()]
for a in animals:
print(a.speak())
Output:
Bark
Purr
Some sound
Duck Typing in Python (Dynamic Polymorphism)
Python uses duck typing: “If it walks like a duck and quacks like a
duck…”
It doesn’t care about the type, only whether the method exists:
class Duck:
def quack(self):
print("Quack!")
class Person:
def quack(self):
print("I'm pretending to be a duck!")
def make_it_quack(thing):
thing.quack()
make_it_quack(Duck()) # Quack!
make_it_quack(Person()) # I'm pretending to be a duck!
Recap:
• Polymorphism = Same method name, different behavior
• Can be used via inheritance or duck typing
• Helps in writing clean, extensible, and flexible code
Nice! You're hitting all the big topics in Python
Let’s break down Iterators, Generators, and Decorators — three
powerful features that really level up your Python game.
1. Iterators
What’s an Iterator?
An iterator is an object that lets you loop through items, one at a time.
To be an iterator, an object must implement:
• __iter__() → returns the iterator object itself
• __next__() → returns the next item (raises StopIteration when
done)
Example:
nums = [1, 2, 3]
it = iter(nums)
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# next(it) would raise StopIteration
You can also make your own custom iterator:
class CountUpTo:
def __init__(self, max):
self.max = max
self.current = 1
def __iter__(self):
return self
def __next__(self):
if self.current > self.max:
raise StopIteration
val = self.current
self.current += 1
return val
for num in CountUpTo(3):
print(num)
2. Generators
What’s a Generator?
A generator is a simpler way to create an iterator using the yield
keyword.
Example:
def count_up_to(n):
current = 1
while current <= n:
yield current
current += 1
for num in count_up_to(3):
print(num)
When you call a generator function, it returns a generator object
you can iterate over.
Why Use Generators?
• Memory-efficient (doesn’t store all items in memory)
• Great for large datasets or infinite sequences
3. Decorators
What’s a Decorator?
A decorator is a function that wraps another function to extend or
modify its behavior — like adding toppings to a pizza .
Basic Decorator:
def my_decorator(func):
def wrapper():
print("Before the function runs")
func()
print("After the function runs")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
Output:
Before the function runs
Hello!
After the function runs
Decorators with Arguments:
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet():
print("Hi!")
greet()
Decorators Are Used For:
• Logging
• Timing
• Authorization
• Caching
• Validation
• Flask/Django routes
TL;DR:
Feature Purpose Key Word(s)
Iterator Looping manually/custom sequences __iter__, __next__
Generator Yield values one at a time (on demand) yield
Decorator Wrap/extend function behavior @decorator
Ooh, custom exceptions — now you’re really stepping into pro Python
dev territory
Custom Exception Classes in Python
Python has many built-in exceptions like ValueError, TypeError, etc. But
sometimes, you want your own specific errors to make your code more
readable, reusable, and easier to debug.
How to Create a Custom Exception
You just define a class that inherits from Exception (or a subclass of it):
class MyCustomError(Exception):
pass
Then you can raise it like any other exception:
raise MyCustomError("Something went wrong!")
Example: Custom Exception with Extra Info
class BalanceTooLowError(Exception):
def __init__(self, message="Balance is too low!", balance=None):
super().__init__(message)
self.balance = balance
def withdraw(amount, balance):
if amount > balance:
raise BalanceTooLowError(balance=balance)
return balance - amount
try:
withdraw(200, 100)
except BalanceTooLowError as e:
print(e) # Output: Balance is too low!
print(e.balance) # Output: 100
Why Use Custom Exceptions?
• Makes your code more expressive
• Helps with precise error handling
• Cleaner than using general-purpose exceptions for everything
You Can Also Inherit from Built-in Errors
If your custom error is similar to a built-in one, inherit from that:
class InvalidAgeError(ValueError):
pass
Pro Tip: Create a Hierarchy
class AppError(Exception): # Base app exception
pass
class DatabaseError(AppError):
pass
class ValidationError(AppError):
pass
This way you can catch all your app-specific errors with one block:
try:
# something risky
pass
except AppError as e:
print(f"Custom app error: {e}")