ASSIGNMENT
1. What do you mean by OOPs? Explain different features and applications of OOPs.
Ans. Object-Oriented Programming (OOP) is an important part of Python. It helps
programmers write code that is easy to manage, reuse, and understand.
In simple words, OOP is a way of writing programs by using classes and objects. A class is
like a blueprint, and an object is something made from that blueprint.
Objects represent real-life things — they have attributes (data about the object) and
methods (actions the object can do).
For example, if you have a class called Car, its attributes could be colour and speed, and its
methods could be start() or stop().
Features of OOPs:
Applications of OOPs:
Areas of applications of OOPs including:
Real Time System Design
AI Expert System
Compiler Design
Office Automation System
Simulation and Modelling
2. Explain the following:
i. Class
ii. Objects
iii. Inheritance
iv. Polymorphism
v. Encapsulation
vi. Data Hiding
vii. Abstraction
Ans.
Class:
A class is a collection of objects. Classes are blueprints for creating objects. A class defines a
set of attributes and methods that the created objects (instances) can have.
Some points on Python class:
Classes are created by keyword class.
Attributes are the variables that belong to a class.
Attributes are always public and can be accessed using the dot (.) operator.
Example: Myclass.Myattribute
Creating a Class:
Here, class keyword indicates that we are creating a class followed by name of the class (Dog
in this case).
class Dog:
species = "Canine" # Class attribute
def __init__(self, name, age):
self.name = name # Instance attribute
self.age = age # Instance attribute
Objects:
An Object is an instance of a Class. It represents a specific implementation of the class and
holds its own data.
An object consists of:
State: It is represented by the attributes and reflects the properties of an object.
Behaviour: It is represented by the methods of an object and reflects the response of
an object to other objects.
Identity: It gives a unique name to an object and enables one object to interact with
other objects.
Creating Object:
Creating an object in Python involves instantiating a class to create a new instance of that
class. This process is also referred to as object instantiation.
class Dog:
species = "Canine" # Class attribute
def __init__(self, name, age):
self.name = name # Instance attribute
self.age = age # Instance attribute
# Creating an object of the Dog class
dog1 = Dog("Buddy", 3)
print(dog1.name)
print(dog1.species)
Output:
Buddy
Canine
Inheritance:
Inheritance allows a class (child class) to acquire properties and methods of another class
(parent class). It supports hierarchical classification and promotes code reuse.
Types of Inheritance:
1. Single Inheritance: A child class inherits from a single parent class.
2. Multiple Inheritance: A child class inherits from more than one parent class.
3. Multilevel Inheritance: A child class inherits from a parent class, which in turn
inherits from another class.
4. Hierarchical Inheritance: Multiple child classes inherit from a single parent class.
5. Hybrid Inheritance: A combination of two or more types of inheritance.
Example: In this example, we create a Dog class and demonstrate single, multilevel
and multiple inheritance. We show how child classes can use or extend parent class
methods.
# Single Inheritance
class Dog:
def __init__(self, name):
self.name = name
def display_name(self):
print(f"Dog's Name: {self.name}")
class Labrador(Dog): # Single Inheritance
def sound(self):
print("Labrador woofs")
# Multilevel Inheritance
class GuideDog(Labrador): # Multilevel Inheritance
def guide(self):
print(f"{self.name}Guides the way!")
# Multiple Inheritance
class Friendly:
def greet(self):
print("Friendly!")
class GoldenRetriever(Dog, Friendly): # Multiple Inheritance
def sound(self):
print("Golden Retriever Barks")
# Example Usage
lab = Labrador("Buddy")
lab.display_name()
lab.sound()
guide_dog = GuideDog("Max")
guide_dog.display_name()
guide_dog.guide()
retriever = GoldenRetriever("Charlie")
retriever.display_name()
retriever.greet()
retriever.sound()
Polymorphism:
Polymorphism in Python means "same operation, different behavior." It allows functions or
methods with the same name to work differently depending on the type of object they are
acting upon.
Types of Polymorphism:
1. Compile-Time Polymorphism:
This type of polymorphism is determined during the compilation of the program. It allows
methods or operators with the same name to behave differently based on their input
parameters or usage. In languages like Java or C++, compile-time polymorphism is achieved
through method overloading but it's not directly supported in Python.
In Python:
True compile-time polymorphism is not supported.
Instead, Python mimics it using default arguments or *args/**kwargs.
Operator overloading can also be seen as part of polymorphism, though it is
implemented at runtime in Python.
Example:
class Calculator:
def add(self, *args):
return sum(args)
calc = Calculator()
print(calc.add(5, 10)) # Two arguments
print(calc.add(5, 10, 15)) # Three arguments
print(calc.add(1, 2, 3, 4)) # Any number of arguments
Output:
15
30
10
2. Run-Time Polymorphism
Run-Time Polymorphism is determined during the execution of the program. It covers
multiple forms in Python:
Method Overriding: A subclass redefines a method from its parent class.
Duck Typing: If an object implements the required method, it works regardless of its
type.
Operator Overloading: Special methods (__add__, __sub__, etc.) redefine how
operators behave for user-defined objects.
Encapsulation:
Encapsulation is the bundling of data (attributes) and methods (functions) within a class,
restricting access to some components to control interactions.
A class is an example of encapsulation as it encapsulates all the data that is member
functions, variables, etc.
Types of Encapsulation:
1. Public Members: Accessible from anywhere.
2. Protected Members: Accessible within the class and its subclasses.
3. Private Members: Accessible only within the class.
Example: In this example, we create a Dog class with public, protected and private
attributes. We also show how to access and modify private members using getter and setter
methods.
class Dog:
def __init__(self, name, breed, age):
self.name = name # Public attribute
self._breed = breed # Protected attribute
self.__age = age # Private attribute
# Public method
def get_info(self):
return f"Name: {self.name}, Breed: {self._breed}, Age: {self.__age}"
# Getter and Setter for private attribute
def get_age(self):
return self.__age
def set_age(self, age):
if age > 0:
self.__age = age
else:
print("Invalid age!")
# Example Usage
dog = Dog("Buddy", "Labrador", 3)
# Accessing public member
print(dog.name) # Accessible
# Accessing protected member
print(dog._breed) # Accessible but discouraged outside the class
# Accessing private member using getter
print(dog.get_age())
# Modifying private member using setter
dog.set_age(5)
print(dog.get_info())
Data Abstraction
Abstraction hides the internal implementation details while exposing only the necessary
functionality. It helps focus on "what to do" rather than "how to do it."
Types of Abstraction:
Partial Abstraction: Abstract class contains both abstract and concrete methods.
Full Abstraction: Abstract class contains only abstract methods (like interfaces).
Example: In this example, we create an abstract Dog class with an abstract method (sound)
and a concrete method. Subclasses implement the abstract method while inheriting the
concrete method.
from abc import ABC, abstractmethod
class Dog(ABC): # Abstract Class
def __init__(self, name):
self.name = name
@abstractmethod
def sound(self): # Abstract Method
pass
def display_name(self): # Concrete Method
print(f"Dog's Name: {self.name}")
class Labrador(Dog): # Partial Abstraction
def sound(self):
print("Labrador Woof!")
class Beagle(Dog): # Partial Abstraction
def sound(self):
print("Beagle Bark!")
# Example Usage
dogs = [Labrador("Buddy"), Beagle("Charlie")]
for dog in dogs:
dog.display_name() # Calls concrete method
dog.sound() # Calls implemented abstract method
Data Hiding:
Data hiding, also known as information hiding, in Object-Oriented Programming (OOP) is a
fundamental principle that restricts direct access to the internal state and implementation
details of an object from outside the class. Its primary purpose is to protect data integrity,
enhance security, and improve modularity and maintainability of code.
Here's how data hiding is typically achieved and its benefits:
Mechanism of Data Hiding:
Access Modifiers:
Programming languages like Java, C++, and Python utilize access modifiers
(e.g., private, protected in Java/C++; name mangling with __ in Python) to control the
visibility of class members (attributes and methods).
Encapsulation:
Data hiding is closely related to encapsulation, which is the bundling of data and the methods
that operate on that data within a single unit (a class). By making data members private and
providing public methods (getters and setters) for controlled access, encapsulation facilitates
data hiding.
Benefits of Data Hiding:
Data Protection and Integrity: Prevents unauthorized or accidental modification of
an object's internal state, ensuring data consistency and validity.
Security: Safeguards sensitive data by restricting direct access and requiring
interaction through controlled interfaces.
Modularity and Maintainability: Allows internal implementation details to be
changed without affecting the external code that uses the class, promoting
independent development and easier maintenance.
Reduced Complexity: Simplifies the external interface of a class, presenting only the
necessary functionalities and hiding the intricate internal workings.
3. What do you mean by Overriding and Overloading? What is function overloading
and operator overloading?
Ans. Overriding: Overriding refers to the ability of a subclass to provide a specific
implementation of a method that is already defined in its superclass. This is a common
feature in object-oriented programming and is fully supported in Python. This allows a
method to behave differently depending on the subclass that implements it.
Overloading in Python is not supported in the traditional sense where multiple
methods can have the same name but different parameters. However, Python supports
operator overloading and allows methods to handle arguments of different types,
effectively overloading by type checking inside methods.
Example of Overriding:
class Parent:
def show(self):
print("Inside Parent")
class Child(Parent):
def show(self):
print("Inside Child")
c = Child()
c.show()
Output:
Inside Child
Method overloading: In many programming languages like C++ or Java, we can define
multiple methods with the same name but different parameter lists. This concept is
called method overloading.
Python does not support method overloading by default. If we define multiple methods with
the same name, only the latest definition will be used.
For example:
def product(a, b):
p=a*b
print(p)
def product(a, b, c):
p = a * b*c
print(p)
product(4, 5, 5)
Output:
100
Function Overloading:
Function overloading, in the traditional sense where multiple functions share the same name
but differ in parameter types or number, is not directly supported in Python like it is in
languages such as C++ or Java. In Python, if you define multiple functions with the same
name, the latest definition will overwrite the previous ones.
However, Python offers several ways to achieve similar behavior, effectively allowing a
single function name to handle different argument signatures:
Default Arguments: Define a function with optional parameters that have default
values. This allows the function to be called with varying numbers of arguments.
def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice") # Output: Hello, Alice!
greet("Bob", "Hi") # Output: Hi, Bob!
Operator overloading:
Operator overloading in Python allows same operator to work in different ways depending on
data type.
Python built-in data types allow + operator can add numbers, join strings or merge
lists and * operator can be used to repeat instances of a string.
Python also allows to do operator overloading for user defined classes by writing
methods like __add__(), __mul__(), __sub__(),, __lt__(), __gt__() and __eq__() to
make objects work for operators like +, *, -, <, > and == respectively.
We cannot override or change the operator behavior of built-in types (like int, str, list)
globally; but we can subclass them (or define your own class) and implement the
special methods