PLC142_(Python)_Unit-4(OOPs) A3
PLC142_(Python)_Unit-4(OOPs) A3
OOP (Object-Oriented Programming) is based on the concept of objects and classes. It helps in organizing code
better by bundling data and functions together.
POP (Procedural-Oriented Programming) follows a step-by-step, top-down approach, where code is written using
functions and procedures without focusing on objects.
OOP provides concepts like inheritance, polymorphism, and encapsulation, making it easier to manage large
projects.
POP is simpler and better suited for small programs where defining multiple functions is enough.
OOP makes code more reusable and scalable, while POP can become messy and harder to maintain as the project
grows.
display(name, age)
def display(self):
print(f"Name: {self.name}, Age: {self.age}")
# Creating objects
s1 = Student("Alice", 20)
s1.display()
A sports car can be a child class of Car (inherits from Car but can add its own methods (like turbo_mode ) and
attributes (spoiler)).
🌟 Summary
✅ OOP organizes programs into objects instead of just functions.
✅ It helps reuse code and makes programs easier to manage.
✅ Four key concepts: Encapsulation, Inheritance, Polymorphism, Abstraction.
✅ Example: A Car class can have attributes (brand, speed) and methods (drive, honk).
📌 2️⃣ Classes and Objects in Python
1️⃣ What is a Class?
A class is a blueprint or template used to create objects.
It defines:
🔹 Example:
Think of a Car blueprint. It describes the car’s color, speed, brand, and behaviors like driving or braking. But this
blueprint itself isn’t a car—it’s just a design.
class classname:
//body of class
Example :
class Car:
pass # Empty class (Just a blueprint)
📌 pass is used when we don’t want to define any methods or attributes yet.
oobject_name = classname(attributes)
Example :
class Car:
def __init__(self, brand, color, speed): # Constructor
self.brand = brand
self.color = color
self.speed = speed
# Creating objects
car1 = Car("BMW", "Red", 200)
car2 = Car("Audi", "Blue", 180)
Every class should have a method with the special name init.
🔹 Example:
class Car:
def __init__(self, brand, color, speed):
self.brand = brand
self.color = color
self.speed = speed
🔍 Definition
self is a reference to the current object.
class Car:
def __init__(brand, color, speed): # ❌ Missing self
brand = brand # ❌ Won't work
color = color # ❌ Won't work
speed = speed # ❌ Won't work
🚨 Problem:
Python does not know that brand belongs to car1 because there is no self .
class Car:
def __init__(self, brand, color, speed): # ✅ `self` is added
self.brand = brand
self.color = color
self.speed = speed
Here, car1 is passed as self . That’s why self.brand = brand assigns "BMW" to car1.brand .
class Car:
def __init__(self, brand, color, speed):
self.brand = brand
self.color = color
self.speed = speed
car1.display_info()
car2.display_info()
car3.display_info()
📌 Each object stores unique data but uses the same class definition.
9️⃣ What Happens in Memory?
When we create an object, Python:
car2.brand = "Audi"
print(car1.brand) # Output: Audi (Changes in car2 affect car1)
📌 Why?
car2 = car1 does not create a new object.
🔟 Summary
✅ A class is a blueprint for objects.
✅ An object is an instance of a class.
✅ is a constructor that runs automatically.
__init__()
# Creating objects
s1 = Student("Alice", 20)
s2 = Student("Bob", 22)
📌 Explanation
✅ and are attributes that store student details.
name age
🔹 Example of Methods
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def display(self): # ✅
Method
print(f"Student: {self.name}, Age: {self.age}")
# Creating object
📌 Explanation
✅ is a method because it is inside a class.
display()
📌 Explanation
✅ is a regular function, not linked to any object.
greet()
s1 = Student("Alice")
s1.greet() # Output: Hello, Alice!
📌 Explanation
✅ is a method because it is inside a class.
greet()
📌 Example:
class Car:
def __init__(self, brand):
self.brand = brand
📌 Explanation
✔ is a method that accepts another object (
compare_speed() ) as an argument.
other_car
3️⃣
PLC142 (Python): Unit-4(OOPs) 8
3️⃣ Returning an Instance from a Method
Methods can return an object (instance) as their result.
This is particularly useful for creating new modified objects inside a method.
s1 = Student("Alice", 85)
s2 = s1.add_bonus(5) # ✅ Creates a new object with updated marks
print(s1.marks) # Output: 85
print(s2.marks) # Output: 90
📌 Explanation
✔ returns a new instance with updated marks.
add_bonus()
📌 Explanation:
✔ is an object of the class.
car1 Car
✔ The same memory location is used, proving that objects are mutable.
3️⃣ Objects Can Be Modified Through Methods
📌 Instead of modifying attributes directly, we can use methods inside a class to change object data.
🔹 Example: Using a Method to Modify an Object
class Car:
def __init__(self, brand, speed):
self.brand = brand
self.speed = speed
📌 Explanation:
✔ allows us to change an object's attribute dynamically.
update_speed()
Instead of rewriting everything, SportsCar can inherit from Car and just add its extra features.
🔹 Basic Syntax:
class Parent:
def __init__(self, param1):
self.param1 = param1 # Parent class attribute
📌 Key Takeaways:
✔ inherits the
SportsCar attribute from
brand . Car
class SportsCar(Car):
def turbo_mode(self): # ✅
New method added in child class
print(f"{self.brand} is in turbo mode!")
📌 Key Takeaways:
✔ The SportsCar class inherits drive() from Car.
✔ It adds a new method turbo_mode() that is specific to sports cars.
class Car:
def __init__(self, brand, speed):
self.brand = brand
self.speed = speed
def drive(self):
print(f"{self.brand} is driving at {self.speed} km/h.")
class SportsCar(Car):
def __init__(self, brand, speed, turbo):
super().__init__(brand, speed) # Calls parent constructor
self.turbo = turbo # New attribute specific to SportsCar
def drive(self): # ✅
Overriding the drive method
super().drive() # ✅
Calls the parent class method
print(f"{self.brand} is in turbo mode with speed {self.speed + 50} km/h!") # Modified behavior
my_car.drive()
🔹 Expected Output
BMW is driving at 200 km/h.
BMW is in turbo mode with speed 250 km/h!
📌 Explanation:
✔ → Calls the parent’s constructor.
super().__init__(brand, speed)
class SportsCar(Car):
def drive(self): # Overriding the parent method
print("SportsCar is driving at high speed!")
my_car = SportsCar()
my_car.drive() # Output: SportsCar is driving at high speed!
📌 Why Override?
✔ Allows the child class to modify the parent's behavior without changing the parent class.
7️⃣Summary
✅ Inheritance allows a class to reuse another class’s attributes and methods.
✅ in Inheritance helps the child class initialize attributes properly.
__init__()
Python has built-in functions that exhibit polymorphism by working on multiple data types.
📌 Key Takeaways:
✔ The same function works with strings, lists, and dictionaries.
len()
class Cat:
def sound(self):
print("Cats meow.")
class Cow:
def sound(self):
print("Cows moo.")
# Creating objects
dog = Dog()
cat = Cat()
cow = Cow()
🔹 Expected Output
Dogs bark.
Cats meow.
Cows moo.
2️⃣ Each method behaves differently depending on the class it belongs to.
sound()
4️⃣ Even though we call the same method name, the output is different for each object.
📌 Key Takeaways for Polymorphism with Class Methods
✅ Different classes have the same method name ( ). sound()
def flight(self):
print("Most of the birds can fly but some cannot.")
class ostrich(Bird):
def flight(self):
print("Ostriches cannot fly.")
# Creating objects
obj_bird = Bird()
obj_spr = sparrow()
obj_ost = ostrich()
# Calling methods
obj_bird.intro()
obj_bird.flight()
obj_spr.intro()
obj_spr.flight()
obj_ost.intro()
obj_ost.flight()
🔹 Expected Output
There are many types of birds.
Most of the birds can fly but some cannot.
2️⃣ The sparrow and ostrich classes (Child Classes) inherit from Bird but override the flight() method with different
behaviors:
📌 Key Takeaways:
✅Same method name ( ) is used in multiple classes.
flight()
✅Child classes override parent class methods with their own behavior.
✅Different objects exhibit different behaviors for the same method.
✅ Polymorphism with class methods → The same method name in different classes.
✅ Polymorphism with inheritance → Child classes override parent methods.
📌 8️⃣ Operator Overloading in Python
📌 What is Operator Overloading?
Operator overloading allows us to change the behavior of operators ( + , - , * , / , == , > , etc.) when used with user-
defined objects.
Python provides special methods (also called magic or dunder methods) that we can define inside a class to
customize how operators work with objects.
For example, instead of writing a method to add two objects, we can simply use + if we define the __add__() method.
p1 = Point(2, 3)
p2 = Point(4, 5)
📌 Problem: Python does not know how to add two Point objects.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Number:
def __init__(self, value):
self.value = value
n1 = Number(3)
n2 = Number(4)
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks
s1 = Student("Alice", 90)
s2 = Student("Bob", 90)
class Number:
def __init__(self, value):
self.value = value
n1 = Number(7)
n2 = -n1 # Calls __neg__
print(n2.value) # Output: -7
📌Explain:
🔹 The method in the class defines how the operator behaves for instances of the class. When
__neg__ Number - -n1 is
called, it invokes and returns a new
__neg__ object with the negated value.
Number
unchanged.
📌 Summary
✅ Operator Overloading allows us to change how operators behave for custom objects.
✅ Python provides special methods like , , and
__add__() for overloading.
__mul__() __eq__()
✅ Unary Operators like , , and can be overloaded to perform actions on a single operand.
- + ~
✅ This makes our code cleaner, more readable, and easier to understand.
Now, your objects can behave just like numbers and strings! 🚀
class Car:
def __init__(self, brand):
self.brand = brand
car1 = Car("BMW")
car2 = Car("BMW")
car3 = car1 # ✅ car3 refers to the same object as car1
print(car1 == car2) # ✅ True (Same brand value)
print(car1 is car2) # ❌ False (Different objects in memory)
print(car1 is car3) # ✅ True (Same memory location)
📌 Explanation:
✔ → ✅ True, because both have the same brand.
car1 == car2
✔ → ✅ True, because
car1 is car3 is just another reference to
car3 car1 .
Assignment ( = ) Both variables point to the same object (no real copy).
Shallow Copy ( copy() ) Creates a new object but references same inner objects.
Deep Copy ( deepcopy() ) Creates a fully independent copy, including inner objects.
car1 = Car("BMW")
car2 = car1 # ❌ Not a real copy, both point to the same object
car2.brand = "Audi"
print(car1.brand) # Output: Audi (Unexpected change!)
📌 Problem:
✔ means both refer to the same object.
car2 = car1
import copy
car1 = Car("BMW")
car2 = copy.copy(car1) # ✅ Shallow copy
car2.brand = "Audi"
print(car1.brand) # Output: BMW (Original remains unchanged)
print(car2.brand) # Output: Audi (Modified copy)
📌 Explanation:
✔ creates a new object.
copy.copy(car1)
class Garage:
def __init__(self, cars):
self.cars = cars
garage2.cars[0].brand = "Tesla"
print(garage1.cars[0].brand) # Output: BMW (Original unchanged)
print(garage2.cars[0].brand) # Output: Tesla (Modified copy)
📌 Explanation:
✔ copy.deepcopy(garage1) creates a fully independent copy.
✔ Changing garage2 does not affect garage1 , unlike a shallow copy.
✅ Assignment ( ) does not create a real copy; both variables point to the same object.
=
✅ Shallow copy ( ) creates a new object but shares references for inner objects.
copy()
✅ Understanding sameness and copying helps avoid unintended changes and improves memory efficiency.
📌🔟 Pure Functions and Generalization
1️⃣ What is a Pure Function?
PLC142 (Python): Unit-4(OOPs) 21
A pure function is a function that:
✔ Does not modify its input values.
✔ Has no side effects (does not change global variables, files, or external data).
✔ Always returns the same output for the same input.
Example of a Pure Function
def square(n):
return n * n # Always returns the same result for the same input
print(square(4)) # Output: 16
print(square(4)) # Output: 16 (Same input, same output)
🔹 This function does not modify anything outside itself, making it pure.
2️⃣ What is a Modifier Function? (Opposite of Pure Functions)
A modifier function changes its input instead of returning a new value.
def double_list(nums):
for i in range(len(nums)):
nums[i] *= 2 # Modifies the original list
my_list = [2, 4, 6]
double_list(my_list)
print(my_list) # Output: [4, 8, 12] (Original list changed)
def greet_john():
print("Hello, John!")
-Made by Vijay