Python Object-Oriented Programming (OOP)
Object-Oriented Programming (OOP) is a programming paradigm that organizes software design around data,
or objects, rather than functions and logic. Python is an inherently object-oriented language,
meaning it fully supports OOP concepts.
The core idea behind OOP is to model real-world entities as objects in your code. These objects combine
both data (attributes) and behavior (methods).
Why OOP?
- Modularity : OOP allows breaking down complex problems into smaller, manageable, and reusable pieces (objects).
- Reusability : Code defined in classes can be reused to create multiple objects, saving development time.
- Maintainability : Changes made in one part of the code are less likely to affect other parts.
- Scalability : Easier to extend and add new features without disrupting existing code.
- Data Hiding/Encapsulation : Protects data from unintended external modification.
Four Pillars of OOP
1. Encapsulation : (Data hiding)
2. Abstraction : (Hiding Complexity)
3. Inheritance : (Reusing Code)
4. Polymorphism : (Many Forms)
Object :
- Everything exists in this world i.e. object, Object have behavior and properties.
- Object is a runtime entity.
Example :
class :
- Class is a Template.
- Class is a blueprint of an object.
- Class is the best example of encapsulation.
- Class is a user-defined data type.
Example: Class and Object
class Employee:
def __init__(self, fname, lname, salary):
self.fname = fname # First name attribute
self.lname = lname # Last name attribute
self.salary = salary # Salary attribute
# Creating objects of the Employee class
James = Employee('James', 'Smith', 50000)
John = Employee('John', 'Johnson', 40000)
# Accessing and printing the salary attributes
print(John.salary, James.salary) # Output: 40000 50000
Other Method :
class Employee:
def __init__(self,fname,lname,salary):
self.fname = fname
self.lname = lname
self.salary = salary
def info(self):
print(self.fname)
print(self.lname)
print(self.salary)
emp = Employee("Robert","Williams",1000)
emp.info()
# Output:
Robert
Williams
1000
What is __init__ :
__init__ is a constructor method, and automatically called to allocate memory, when a new object is created.
What is Self( ):
self( ) is used to represent the instance (object) of the class.
self( ) gives access to instance attribute - (self.age).
self( ) is automatically passed by python.
Types of variable in Python OOP :
A. Instance Variables: (Object level variable)
B. Static Variables: (Class level variable)
C. Local Variables: (Method level variable)
A. Instance Variables :
For every object a seperate copy will be created.
Where can we declare instance variables :
- Outside of the class by using object reference variables
1. Inside Constructor (__init__( )) using self.
class Student:
def __init__(self, name, age):
self.name = name # Instance variable
self.age = age # Instance variable
2. Inside Instance Method using self.
class Student:
def set_grade(self, grade):
self.grade = grade # Instance variable created in method
3. Outside the Class using object reference.
s1 = Student("Vikas", 23)
s1.city = "Delhi" # city is an instance variable added from outside
B. Static Variables :
Static variables (also called class variables) are variables that are shared among all objects of the class.
Only one copy of the variable exists at the class level, not per object.
How to Declare Static (Class) Variables in Python :
Static variable = Variable that belongs to the class, not to individual objects.
1. Directly inside the class (outside all methods).
class Student:
school_name = "ABC School" # Static variable
2. Inside the constructor using class name.
class Student:
school_name = "ABC School" # Static variable
Note: Use class name, not self, because static variable is not instance-specific.
3. Inside instance method using class name.
class Student:
def set_school(self):
Student.school_name = "XYZ School"
Avoid using self.school_name — it will create an instance variable instead.
4. Inside class method using cls or class name.
class Student:
@classmethod
def set_school(cls):
cls.school_name = "LMN School"
cls is automatically passed to class methods — recommended way for modifying static variables.
5. Inside static method using class name.
class Student:
@staticmethod
def set_school():
Student.school_name = "PQR School"
Static method does not take self or cls, so use class name.
6. From outside the class using class name.
Student.school_name = "DEF School"
This also directly modifies the class variable — and affects all instances.
C. Local Variables :
Local variables are variables that are declared inside a method/function/block and can be accessed only within that method.
These variables are created when the function is called and destroyed when the function ends.
Key Characteristics of Local Variables :
| Feature |
Description |
Scope |
Limited to the method/block where defined |
Lifetime |
Exists only while the method is running |
Accessed by |
Directly inside the function (no self, no cls) |
Shared between objects? |
❌ No (Each call has its own local variables) |
Memory location |
Inside method’s stack frame |
Not an attribute |
Not part of the object (__dict__) or class |
Types of Methods :
In Python's object-oriented programming, there are 3 main types of methods defined inside a class:
1. Instance Method :
- Most commonly used.
- Object related method.
- Any method consists of Instance variable-Instance method
- Inside the instance method, First Keyword, i.e. self is compulsory.
class Student:
def __init__(self, name):
self.name = name
def greet(self): # Instance method
print(f"Hello, {self.name}")
2. Class Method :
- Works with class variables (shared among all objects).
- Cannot access instance variables directly.
- Decorated with @classmethod.
class Student:
school = "DPS" # Class variable
@classmethod
def get_school(cls): # Class method
print(f"School Name: {cls.school}")
- Called using: ClassName.method_name( ) or object.method_name( )
- First argument is always cls.
3. Static Method :
- Does not access class or instance variables.
- Like a regular function placed inside a class for logical grouping.
- Decorated with @staticmethod.
class MathUtils:
@staticmethod
def add(a, b): # Static method
return a + b
- Called using: ClassName.method_name( ) or object.method_name( )
- No self or cls needed.
Note :
- Instance methed - (No decorator required)
- class method - @classmethed is (mandatory)
- static method - @staticmethed is (optional)
- Instance variables - (Instance method)
- static variable - (class method)
- Instance + Static - (Instance method)
- Instance + local - (Instance method)
- static + local - (Class method)
- local - (static method)
No decorator:- only two options.
- Instance method
- Static method
Setter and Getter Methods :
In Object-Oriented Programming, getter and setter methods are used to access and modify private data members of a class safely.
Why use Getter and Setter?
- To encapsulate data.
- To control access to variables.
- To add validation logic while setting or getting a value.
1. Getter Method :
Used to read/access the value of a private variable.
class Student:
def __init__(self, name):
self.__name = name # private variable using __
def get_name(self): # Getter method
return self.__name
2. Setter Method :
Used to modify/update the value of a private variable.
def set_name(self, new_name): # Setter method
if isinstance(new_name, str) and new_name != "":
self.__name = new_name
else:
print("Invalid name")
Full Example :
class Student:
def __init__(self, name):
self.__name = name # private variable
def get_name(self): # Getter
return self.__name
def set_name(self, new_name): # Setter
if isinstance(new_name, str) and new_name != "":
self.__name = new_name
else:
print("Invalid name")
# Usage
s = Student("Vikas")
print(s.get_name()) # Output: Vikas
s.set_name("Rohan") # Update name
print(s.get_name()) # Output: Rohan
s.set_name(123) # Invalid name
Summary :
| Method Type |
Purpose |
Syntax |
Getter |
Access private data |
get_variable() |
Setter |
Modify private data |
set_variable(value) |
Employee Salary Increment?
class Employee:
increment = 2.5
def __init__(self,fname,lname,salary):
self.fname = fname
self.lname = lname
self.salary = salary
def increase(self):
self.salary = self.salary * Employee.increment
Michael = Employee("Michael","Brown",5000)
Michael.increase()
print(Michael.salary)
# Output: 12500.0
celsius to fahrenheit?
def ctof(c):
f = (c*9/5)+32
print(f)
ctof(10)
ctof(20)
ctof(30)
# Output:
50.0
68.0
86.0
Other Method : celsius to fahrenheit?
class Ctof:
def __init__(self,c):
self.c = c
def ctof(self):
f = (self.c*9/5) + 32
print(f)
c1 = Ctof(10)
c2 = Ctof(20) #object
c3 = Ctof(30)
c4 = Ctof(40)
c1.ctof()
c2.ctof()
c3.ctof()
c4.ctof()
# Output:
50.0
68.0
86.0
104.0
Employee, name, employeeid, salary :
class Employee:
def __init__(self,name,employeeid,salary):
self.name = name
self.employeeid = employeeid # These three are instance variables.
self.salary = salary
def talk(self): #its not a function ,talk is a method
print("hello my name is:",self.name)
print("hello my employeeid is:",self.employeeid)
print("hello my salary is:",self.salary)
emp1 = Employee("William",1234,56789)
emp2 = Employee("Jones",1290,56700)
emp1.talk()
emp2.talk()
# Output:
hello my name is: William
hello my employeeid is: 1234
hello my salary is: 56789
hello my name is: Jones
hello my employeeid is: 1290
hello my salary is: 56700
Movie Example :
class Movie:
def __init__(self,name,hero,heroine,rating):
self.name = name
self.hero = hero
self.heroine = heroine
self.rating = rating
def info(self):
print("movie name:",self.name)
print("movie hero:",self.hero)
print("movie heroine:",self.heroine)
print("movie rating:",self.rating)
mov = Movie("PK","AK","AS",8)
mov.info()
# Output:
movie name: PK
movie hero: AK
movie heroine: AS
movie rating: 8
Movie Example through for loop :
class Movie:
def __init__(self,name,hero,heroine,rating):
self.name = name
self.hero = hero
self.heroine = heroine
self.rating = rating
def info(self): #info is method
print("movie name:",self.name)
print("movie hero:",self.hero)
print("movie heroine:",self.heroine)
print("movie rating:",self.rating)
movies = [Movie("PK","AK","AS",10),Movie("AB","CD","EF",10),Movie("GH","IJ","KL" ,6),Movie("MN","OP","QR",0 )]
for mov in movies:
mov.info()
# Output:
movie name: PK
movie hero: AK
movie heroine: AS
movie rating: 10
movie name: AB
movie hero: CD
movie heroine: EF
movie rating: 10
movie name: GH
movie hero: IJ
movie heroine: KL
movie rating: 6
movie name: MN
movie hero: OP
movie heroine: QR
movie rating: 0
How to access the instance variable - Within the class by using self.
class Test:
def __init__(self):
self.a = 20 # Instance variable
self.b = 30 # Instance variable
def m1(self): # Instance method
print(self.a) # Accessing a using self
print(self.b) # Accessing b using self
t = Test() # Creating object
t.m1() # Calling instance method
# Output -
Value of a: 20
Value of b: 30
How to Delete Instance Variables in Python.
In Python, we can delete instance variables using the del keyword with self inside the class.
class Test:
def __init__(self):
self.a = 10
self.b = 20
self.c = 30
self.d = 40
def m1(self):
del self.b # Deleting instance variable 'b'
# Create an instance of the Test class
t = Test()
# Print instance variables before deletion
print("Before deletion:", t.__dict__)
# Call method to delete variable 'b'
t.m1()
# Print instance variables after deletion
print("After deletion:", t.__dict__)
# Output -
Before deletion: {'a': 10, 'b': 20, 'c': 30, 'd': 40}
After deletion: {'a': 10, 'c': 30, 'd': 40}
How to Modify and Add Instance Variables Dynamically in Python.
class Test:
def __init__(self):
self.a = 10 # Instance variable initialized
def m1(self):
self.a = 777 # Modifying existing variable
self.b = 888 # New instance variable added
t = Test()
# Print instance variables before method call
print(t.__dict__) # {'a': 10}
# Modify instance variables using method
t.m1()
print(t.__dict__) # {'a': 777, 'b': 888}
# Modify 'b' and add new 'c' from outside the class
t.b = 1000
print(t.__dict__) # {'a': 777, 'b': 1000}
t.c = 2000
print(t.__dict__) # {'a': 777, 'b': 1000, 'c': 2000}
Print Fibonacci Series up to n.
def fib(n):
"""Print a Fibonacci series up to n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a + b
# Call the function with desired limit
fib(2000)
# Output : 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
Return Fibonacci Series up to n (as a List).
def fib2(n):
"""Return a list containing the Fibonacci series up to n."""
result = []
a, b = 0, 1
while a < n:
result.append(a)
a, b = b, a + b
return result
# Call the function and store result
f100 = fib2(100)
print(f100)
# Output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
Understanding Static Variables with __dict__ Example
class Test:
a = 100 # Static variable
def __init__(self):
self.b = 200 # Instance variable
def m1(self):
self.c = 300 # Instance variable (added to object)
@classmethod
def m2(cls):
cls.d = 400 # Class variable (added to class)
Test.e = 500 # Also class variable
@staticmethod
def m3():
Test.f = 600 # Static method adding class variable
# Create object
t = Test()
# 1. Before calling any method
print(Test.__dict__) # Only a, not b/c/d/e/f
# 2. Call instance method (modifies object only)
t.m1()
print(Test.__dict__) # No change in class dict
# 3. Call class method (adds d and e)
t.m2()
print(Test.__dict__) # Now includes d and e
# 4. Call class method again (values overwrite)
t.m2()
print(Test.__dict__) # d and e values still there
# Output :
{'__module__': '__main__', 'a': 100, '__init__': , 'm1': , 'm2': , 'm3': , '__dict__': , '__weakref__': , '__doc__': None}
{'__module__': '__main__', 'a': 100, '__init__': , 'm1': , 'm2': , 'm3': , '__dict__': , '__weakref__': , '__doc__': None}
{'__module__': '__main__', 'a': 100, '__init__': , 'm1': , 'm2': , 'm3': , '__dict__': , '__weakref__': , '__doc__': None, 'd': 400, 'e': 500}
{'__module__': '__main__', 'a': 100, '__init__': , 'm1': , 'm2': , 'm3': , '__dict__': , '__weakref__': , '__doc__': None, 'd': 400, 'e': 500}
Difference Between Instance Variable and Static Variable in Python.
class Test:
a = 777 # Static variable (class-level)
def __init__(self):
self.a = 888 # Instance variable (object-level)
# Create object
t = Test()
# Access instance variable
print(t.a) # Output: 888
# Access static variable
print(Test.a) # Output: 777
# Output :
888
777
How to Access Static Variables in Python (OOP).
- Inside the constructor Either by self or by class name.
- Inside the Instance method Either by self or class name.
- Inside the class method Either by cls or by class name.
- Inside the static method By class name.
- Outisde the class Either by object ref or by class name.
class Test:
a = 10 # Static variable
def __init__(self):
print(self.a) # 1 Access via self (inside constructor)
print(Test.a) # 1 Access via class name
def m1(self):
print(self.a) # 2 Access via self (inside instance method)
print(Test.a) # 2 Access via class name
@classmethod
def m2(cls):
print(cls.a) # 3 Access via cls (inside class method)
print(Test.a) # 3 Access via class name
@staticmethod
def m3():
print(Test.a) # 4 Access via class name (inside static method)
# 5 Access from outside the class
t = Test() # constructor runs
t.m1()
t.m2()
t.m3()
print(t.a) # 5 via object reference
print(Test.a) # 5 via class name
# Output :
10
10
10
10
10
10
10
10
10
Important Note :
Even when accessed using self, cls, or object reference, the value still comes from the class-level static variable (unless overridden in the instance).
How to Update Static Variable Inside Constructor in Python.
class Test:
a = 10 # Static variable
def __init__(self):
Test.a = 100 # Updating static variable inside constructor
# Create object
t = Test()
# Print updated static variable
print(Test.a)
# Output : 100
Where Can We Modify Static Variables in Python?.
- By using the class name (Inside or outside the class).
- Inside the class method by using cls variable also.
class Test:
a = 10 # Static (class-level) variable
def __init__(self):
Test.a = 888 # Modify inside constructor
def m1(self):
Test.a = 10000 # Modify inside instance method
@classmethod
def m2(cls):
Test.a = 9090 # Modify using class name
cls.a = 2222 # Modify using cls
@staticmethod
def m3():
Test.a = 22122 # Modify inside static method
# Modify from constructor
t = Test()
print(Test.a) # 888
# Modify from instance method
t.m1()
print(Test.a) # 10000
# Modify from class method
t.m2()
print(Test.a) # 2222
# Modify from static method
t.m3()
print(Test.a) # 22122
# Modify directly using class name
Test.a = 44322
print(Test.a) # 44322
Understanding Class, Instance, and Local Variables in Python.
class Test:
a = 10 # Class variable
def __init__(self):
Test.a = 1000 # Modify class variable
a = 100 # Local variable (only in constructor)
self.a = 90 # Instance variable
print(self.a) # Output: 90 (instance variable)
print(Test.a) # Output: 1000 (modified class variable)
# print(b) # ❌ Will raise NameError (b not defined here)
def m1(self):
b = 10 # Local variable
print(b) # Output: 10
# Create object and call methods
t = Test()
t.m1()
# Output :
90
1000
10
Difference Between Class and Instance Variables (Shadowing Example).
class Test:
x = 10 # Class variable
def __init__(self):
self.y = 20 # Instance variable
# Create two objects
t1 = Test()
t2 = Test()
# Initial values
print(t1.x, t1.y) # 10 20
print(t2.x, t2.y) # 10 20
# Modify values
t1.x = 888 # Creates a new instance variable 'x' for t1
t2.y = 1000 # Modifies instance variable 'y' for t2
# Values after modification
print(t1.x, t1.y) # 888 20 (t1.x is now instance-level)
print(t2.x, t2.y) # 10 1000 (t2.x still refers to class-level)
# Output :
10 20
10 20
888 20
10 1000
Important Note :
When you assign t1.x = 888, Python does NOT change the class variable.
It creates a new instance variable x only for t1, which shadows the class variable.
You can confirm this by printing :
print(Test.__dict__) # Class variables only
print(t1.__dict__) # Instance variables of t1
print(t2.__dict__) # Instance variables of t2
How Instance Methods Can Shadow Class Variables in Python.
class Test:
a = 10 # Class variable
def __init__(self):
self.b = 20 # Instance variable
def m1(self):
self.a = 888 # Creates an instance variable named 'a'
self.b = 999 # Updates the instance variable 'b'
# Create two instances
t1 = Test()
t2 = Test()
# Modify t1 only
t1.m1()
# Print values for t1
print(t1.a, t1.b) # Output: 888 999
# Print values for t2 (unaffected)
print(t2.a, t2.b) # Output: 10 20
# Output :
888 999
10 20
Modifying Class Variables Using Class Methods in Python (With Instance Variable Restriction).
class Test:
a = 10 # Class variable
def __init__(self):
self.b = 20 # Instance variable
@classmethod
def m1(cls):
cls.a = 888 # Modify class variable
# cls.b = 999 # Error: 'b' is an instance variable, not accessible via cls
# Create two objects
t1 = Test()
t2 = Test()
# Call class method using t1
t1.m1()
# Access variables
print(t1.a, t1.b) # 888 20
print(t2.a, t2.b) # 888 20
print(Test.a) # 888
# print(Test.b) # ❌ This would raise AttributeError
# Output :
888 20
888 20
888
Store and Display Multiple Student Records Using Setters and Getters.
class Student:
def setName(self, name):
self.name = name # Set instance variable 'name'
def getName(self):
return self.name # Get instance variable 'name'
def setMarks(self, marks):
self.marks = marks # Set instance variable 'marks'
def getMarks(self):
return self.marks # Get instance variable 'marks'
# Create an empty list to store student objects
students_list = []
# Input: Number of students
num_students = int(input("Enter the number of students: "))
# Input student details and store objects in the list
for i in range(num_students):
s = Student()
name = input(f"Enter name of student {i+1}: ")
marks = input("Enter marks: ")
s.setName(name)
s.setMarks(marks)
students_list.append(s)
# Display all student records
for s in students_list:
print("\nStudent Name:", s.getName())
print("Student Marks:", s.getMarks())
# Output :
Enter the number of students: 2
Enter name of student 1: Ram
Enter marks: 87
Enter name of student 2: Shyam
Enter marks: 90
Student Name: Ram
Student Marks: 87
Student Name: Shyam
Student Marks: 90
Inner Classes (Class Inside a Class).
- Classes defined inside another class.
- Inner class cannot be used independently without outer class
class Outer:
def __init__(self):
print("Outer class object creation...")
class Inner:
def __init__(self):
print("Inner class object creation...")
def m1(self):
print("Inner Class Method")
# Create outer class object
o = Outer()
# Create inner class object via outer class object
i = o.Inner()
i.m1()
# Output :
Outer class object creation...
Inner class object creation...
Inner Class Method
What is Garbage Collector (GC)?
Garbage Collector (GC) is a part of Python’s memory management system, and it's managed by the Python Virtual Machine (PVM).
GC is responsible for automatically deleting unused (unreachable) objects from memory to free up space.
Key Points :
- Reference counting is the primary technique.
- When the reference count of an object becomes zero, it becomes eligible for garbage collection.
- GC looks for cyclic references (objects referring to each other but not used anywhere else).
Example :
import gc
class Test:
def __del__(self):
print("Destructor called, object deleted.")
t1 = Test()
t2 = t1
del t1
del t2
# Forcing garbage collection manually
gc.collect()
# Output : Destructor called, object deleted.