Module 5
Object-oriented programming: A powerful style of programming in which data and the operations
that manipulate it are organized into objects.
Object-oriented language: A language that provides features, such as user-defined classes and
inheritance, that facilitate object-oriented programming.
Objects are Mutable
In Python, objects are mutable if their internal state (attributes) can be changed after creation.
A Rectangle object has attributes like:
position (corner.x, corner.y)
size (width, height)
We can change these values without creating a new object.
In Python, objects of user-defined classes are mutable by default.
This means once an object is created, we can modify its attributes without
creating a new object.
When we change an attribute, the change is reflected in the same object
memory location.
Examples of mutable objects:
Class objects
Lists
Dictionaries
Examples of immutable objects:
Integers
Strings
Tuples
1
Example
class Employee:
def __init__(self, name, salary):
[Link] = name
[Link] = salary
e1 = Employee("Anil", 25000)
[Link] = 30000
print([Link]) print([Link])
Explanation
e1 is an object of class Employee
Salary value is changed after object creation
The same object is updated → object is mutable
Why mutability is important
Allows updating object properties
Useful for real-world modeling (moving, resizing, updating)
Avoids creating new objects repeatedly
Makes programs efficient and flexible
Sameness
Sameness refers to whether two variables refer to the same object in memory.
Python provides the is operator to check identity
is → checks same object
== → checks same value 👉
Two variables can:
Refer to the same object
Refer to different objects with same values
2
The word “same” can have two different meanings, both in real life and in programming.
In Object-Oriented Programming, this idea is called object sameness or equality.
Two meanings of “same”
1. Same data, different objects
Like Alice and Bob having the same car model
Objects look the same but are separate
2. Same object (same identity)
Like Alice and Bob having the same mother
Only one object, referred to by two names
Ex:
class Laptop:
def __init__(self, brand):
[Link] = brand
l1 = Laptop("HP")
l2 = l1 # same object
l3 = Laptop("HP") # different object
print(l1 is l2)
print(l1 is l3)
print([Link] == [Link])
Output:
True
False
True
Explanation
l1 and l2 refer to the same memory object
l3 is a separate object, though value is same
3
Copying Objects
Copying is the process of creating a new object with the same content as an existing object.
Copying helps avoid unexpected changes caused by aliasing.
When copying objects, we must ensure:
Whether the new object shares internal data Or has completely independent data Python supports:
1. Shallow Copy
2. Deep Copy
Shallow Copy
A shallow copy creates a new object, but it does not copy embedded objects.
Instead, it copies references to them.
Ex:
import copy
p1 = Point(3, 4)
p2 = [Link](p1)
print(p1 is p2) # False
print(same_coordinates(p1, p2)) # True
Output
[70, 80, 90, 100]
[70, 80, 90, 100]
Explanation
r1 and r2 are different objects
But both share the same list
Change in one affects the other
4
Deep Copy
A deep copy creates:
a new object
new copies of all embedded objects
Example
import copy
r1 = Result([70, 80, 90]) r2 = [Link](r1)
[Link](100)
print([Link]) print([Link])
Output
[70, 80, 90]
[70, 80, 90, 100]
Explanation
Both objects and internal data are independent
Changes do not affect the original object
5
Inheritance: Pure functions, Modifiers, Generalization, Operator Overloading,
Polymorphism
Pure Functions :
A pure function is a function that:
1. Always produces the same output for the same input
2. Does not modify external state (no side effects)
Pure functions do not depend on global variables
They do not change object attributes, files, or external data
They are predictable, reusable, and easy to test
Widely used in functional programming and safe OOP design
Example: add_time as a Pure Function
def add_time(t1, t2):
h = [Link] + [Link]
m = [Link] + [Link]
s = [Link] + [Link]
sum_t = MyTime(h, m, s)
return sum_t
Why this is a pure function?
t1 and t2 are not changed
A new MyTime object is created
No side effects (no print, no input, no global changes)
Impure Function Example:
total = 0
def add_value(x):
global total total += x
This is not pure because it modifies a global variable.
6
Modifiers
A modifier is a function or method that changes (modifies) the object passed to it as a parameter.
Unlike a pure function, a modifier does not create a new object—it updates the existing object
directly.
Modifiers are functions or methods that change the state of an object.
They modify object attributes
Changes are visible to the caller
They work on objects, not on visibility
Example: add_time as a Pure Function
def add_time(t1, t2):
h = [Link] + [Link]
m = [Link] + [Link]
s = [Link] + [Link]
sum_t = MyTime(h, m, s)
return sum_t
This is a pure function
t1 and t2 are not changed
A new MyTime object is created
No side effects (no print, no input, no global changes)
Using the Pure Function
current_time = MyTime(9, 14, 30)
bread_time = MyTime(3, 35, 0)
done_time = add_time(current_time, bread_time)
print(done_time)
Output
12:49:30
7
Difference between Pure Function and Modifier Function in Python
Modifier
Aspect Pure Function
Function
A function that A function
does not change that modifies
Definition
the objects the state of an
passed to it object
Original object Original
Effect on
remains object is
object
unchanged changed
Creates and Does not
Object
returns a new create a new
creation
object object
Has side
Side effects No side effects
effects
Usually returns Usually
Return value
a new object returns None
Object state
Object state
remains the
Object state changes after
same after
function call
function call
Less safe,
Safer and may affect
Safety
predictable other
references
Reusability Highly reusable Less reusable
Calculations, Updating
Example use
transformations object values
Object-
Functional
Programming oriented
programming
style programming
style
style
8
Generalization
Generalization means solving a problem in a more abstract and general way instead of handling
many special cases separately.
In programming, generalization often makes code:
simpler
more reliable
easier to extend
—even though it may seem harder at first.
Generalization using Time Example
When working with time (hours : minutes : seconds), a naive approach is:
Add seconds
Handle carry to minutes
Handle carry to hours
Deal with borrowing for subtraction
This leads to:
❌ many special cases
❌ long and error-prone code
Generalized Approach (Base Conversion Idea)
Time can be treated as a base-60 number:
60 seconds = 1 minute
60 minutes = 1 hour
Steps:
1. Convert time → total seconds (base-10)
2. Perform arithmetic (+ or −)
3. Convert back → hours, minutes, seconds
This is generalization.
Naive (Specialized) Generalized
Many if/while checks Simple arithmetic
Hard to debug Easy to debug Why Generalization is Better
9
Error-prone Reliable
Hard to extend Easy to extend
Overloading + Operator (__add__)
Example: MyTime class
class MyTime:
def __init__(self, h, m, s):
[Link] = h
[Link] = m
[Link] = s
def to_seconds(self):
return [Link]*3600 + [Link]*60 + [Link]
def __add__(self, other):
return MyTime(0, 0, self.to_seconds() + other.to_seconds())
def __str__(self):
total = self.to_seconds()
h = total // 3600
m = (total % 3600) // 60
s = total % 60
return f"{h:02}:{m:02}:{s:02}"
Usage
t1 = MyTime(1, 15, 42)
t2 = MyTime(3, 50, 30)
t3 = t1 + t2
print(t3)
Output
05:06:12
t1 + t2 is internally:
10
Overloading + for Point
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
p1 = Point(3, 4)
p2 = Point(5, 7)
print((p1 + p2).x, (p1 + p2).y) # 8 11
Polymorphism
Polymorphism means the same function or operation can work with objects of different types, as long
as those objects support the required operations.
The word polymorphism means “many forms.”
What is Polymorphism?
Polymorphism allows a single function or operation to behave differently depending on the type of
object passed to it.
One function
Multiple data types
Same operation, different behaviour
Polymorphic Function multadd
def multadd(x, y, z):
return x * y + z
This function works for any types where:
x * y is valid
(x * y) + z is valid
11
Using Numbers
multadd(3, 2, 1)
Output: 7
Polymorphism allows a single function or operation to behave differently depending on the type of
object passed to it.
One function
Multiple data types
Same operation, different behavior
Why Polymorphism is Needed
Normally, methods work on specific types.
But many operations (like +, *, print) are common across types.
Polymorphism allows us to:
write generic functions
reduce code duplication
make programs flexible and reusable
Function multadd
def multadd(x, y, z):
return x * y + z
This function works for any types where:
x * y is valid
(x * y) + z is valid
Using Numbers
multadd(3, 2, 1)
✔ Output: 7
12
Duck Typing (Key Rule in Python)
Python follows duck typing:
“If it looks like a duck and quacks like a duck, it is a duck.”
Meaning:
If an object supports all the operations used inside a function, then the function can work with that
object.
Python does not care about the object’s class — only about its behavior.
🔹 Example: front_and_back Function
def front_and_back(front):
import copy
back = [Link](front)
[Link]()
print(str(front) + str(back))
Operations used:
copy
reverse
str
Exceptions: Catching Exceptions, Raising your own exceptions
Exceptions
An exception is a runtime error that occurs while a program is executing.
When an exception happens:
Python stops the program
It prints a traceback
The last line shows:
o Type of error
o Error message
13
Examples of Exceptions
1. Division by zero
print(55 / 0)
Exception:
ZeroDivisionError: division by zero
2. Accessing invalid list index
a = []
print(a[5])
Exception:
IndexError: list index out of range
3. Modifying a tuple
tup = ("a", "b", "d", "d")
tup[2] = "c"
Exception:
TypeError: 'tuple' object does not support item assignment
Why Do We Catch Exceptions?
Sometimes an error may happen, but we don’t want the program to crash.
Instead, we want to:
Handle the error
Show a friendly message
Continue execution
This is done using try and except
Ex:
filename = input("Enter a file name: ")
try:
f = open(filename, "r")
except FileNotFoundError:
print("There is no file named", filename)
try: Code that may cause an error
except: Runs only if that error occurs
If the file does not exist → program does not crash
14
Structure of try Statement
A try statement can have four parts:
try:
# risky code
except ExceptionType:
# runs if exception occurs
else:
# runs if NO exception occurs
finally:
# always runs
Only try is mandatory
except, else, finally are optional
Raising Our Own Exceptions in Python
Can a program create its own exception?
Yes.
If a program detects an error condition, it can deliberately raise an exception using the raise keyword.
This is useful when:
Input is invalid
A rule is violated
You want to stop execution and report a meaningful error
Example: Validating User Input (Age)
def get_age():
age = int(input("Please enter your age: "))
if age < 0:
my_error = ValueError("{0} is not a valid age".format(age))
raise my_error
return age
15
Explanation
age = int(input(...))
Takes age from the user
if age < 0:
Checks for invalid input
ValueError("{0} is not a valid age".format(age))
Creates an exception object with a custom message
raise my_error
Raises the exception and stops normal execution
return age
Runs only if no exception occurs
When raise is executed:
1. The function immediately stops
2. The exception is returned to the calling function
3. Python searches for a matching try–except
4. If found → exception is handled
5. If not found → Python prints a traceback and exits
This process is called: Unwinding the Call Stack
Example: call chain:
A → B → C → D → get_age()
If get_age() raises an exception:
get_age() exits
Control returns to D
Then to C, B, A
Until a try–except handles it
Program Output Example
Valid Input
Please enter your age: 42
16
Please enter your age: -2
Traceback (most recent call last):
ValueError: -2 is not a valid age
Error message shows:
Exception type (ValueError)
Custom message (-2 is not a valid age)
Built-in Exceptions
Python provides many built-in exceptions like:
ValueError
TypeError
IndexError
FileNotFoundError
Use the exception type that best matches the error
Raise Statement:
The raise statement is used to deliberately trigger an exception in a program.
User-Defined Exception:
An exception raised by the programmer to signal an error condition.
Unwinding the Call Stack:
The process of passing an exception back through calling functions until it is handled.
17
Inheritance in Object-Oriented Programming
Inheritance is one of the most important features of Object-Oriented Programming.
It allows us to create a new class from an existing class.
The existing class is called the Parent class (or Base class)
The new class is called the Child class (or Subclass)
The child class automatically gets all the methods and attributes of the parent class.
Why is it called Inheritance?
Just like a child inherits qualities from parents,
a child class inherits properties and methods from its parent class.
Main Advantages of Inheritance
1. Code Reusability
We can reuse existing code
No need to rewrite common methods
2. Easy Extension
New methods can be added to the child class
Parent class code remains unchanged
3. Better Program Structure
Represents real-world relationships
Makes the program easier to understand
4. Less Code, More Functionality
Complex programs can be written in a simple and concise way
18
Ex:
class Animal:
def sound(self):
print("Animal makes a sound")
class Dog(Animal):
def bark(self):
print("Dog barks")
Animal → Parent class
Dog → Child class
Dog inherits sound() method from Animal
Disadvantages of Inheritance
Code Readability Issues
Methods may be defined in different files or classes
Difficult to trace where a method is implemented
Overuse Can Be Harmful
Some problems do not naturally fit inheritance
Using inheritance unnecessarily can make programs confusing
Alternative Approaches Exist
Many tasks can be done without inheritance
Sometimes composition is better than inheritance
19
20