PPS - NOTES - Unit-5 .
PPS - NOTES - Unit-5 .
Unit V
Object Oriented Programming
Programming Paradigms-monolithic, procedural, structured and object oriented, Features of
Object oriented programming-classes, objects, methods and message passing, inheritance,
polymorphism, containership, reusability, delegation, data abstraction and encapsulation.
Classes and Objects: classes and objects, class method and self object, class variables and object
variables, public and private members, class methods.
Programming Paradigms:
Monolithic:
A monolithic programming paradigm refers to a software architecture in which all components of
the system are combined into a single, unified executable. This is in contrast to other architectural
styles such as microservices, where the system is broken down into smaller, independently
deployable components. Monolithic architectures are simpler to develop and maintain, but can
become unwieldy as the system grows in complexity and scale.
An example of a monolithic architecture would be a traditional 3-tier web application. The
application would have a single codebase containing all the layers: the user interface, the business
logic, and the data storage. All the components are tightly coupled and interact with each other
through a single process. All the code is executed in a single process and the entire application is
deployed as a single unit.
For example, a monolithic e-commerce application would have all the code for the user interface,
product catalog, shopping cart, and order management in a single codebase and would be deployed
as a single unit. This can make it difficult to scale, update or maintain the application as it grows in
complexity.
1. Traditional 3-tier web applications: These are applications where all the layers of the system
(user interface, business logic, and data storage) are combined into a single codebase and
deployed as a single unit.
2. Desktop applications: These are applications where all the code for the user interface,
business logic, and data storage are combined into a single executable file.
3. Embedded systems: These are applications where all the code for the user interface,
hardware control, and data storage are combined into a single unit.
4. Gaming engines: These are applications where all the code for the game engine, physics,
and graphics are combined into a single unit.
5. Enterprise resource planning (ERP) systems: These are applications where all the code for
the different business modules such as finance, inventory, and human resources are
combined into a single unit.
6. CRM systems: These are applications where all the code for the different business modules
such as sales, marketing, and customer support are combined into a single unit.
Procedural programming:
In procedural programming, the focus is on the procedures themselves, rather than on the data
that the procedures operate on. Programs are written as a series of instructions, in which the
computer follows a set of steps in a linear, sequential manner. Programs are often structured as a
series of procedures, each of which performs a specific task.
Examples of programming languages that support procedural programming include C, Pascal, and
Fortran.
It's worth noting that procedural programming is still widely used today, and it's considered as one
of the traditional programming paradigm, and it's not limited to these old languages. Many modern
programming languages, such as C#, C++ and Java, have support for procedural programming and
it's used in many domains.
Structured programming:
In structured programming, control flow statements like if-else, for-loops and while-loops are used
to direct the flow of execution through the program. Functions and procedures are used to group
related code into logical units that can be easily reused and tested.
Structured programming builds on top of the procedural programming paradigm and it's considered
as a subset of it. It's an approach that enforces a logical and clear structure in the program and it's
advocated to make the program more understandable and easy to maintain.
Examples of programming languages that support structured programming include C, Pascal, and
Fortran, and like procedural programming, many modern programming languages, such as C#, C++
and Java, have support for structured programming and it's widely used in many domains.
Encapsulation: The idea of encapsulation is to group related data and behavior together
within an object, and hide the implementation details from the outside world. This allows
for data and behavior to be protected from accidental modification and ensures that the
implementation can be changed without affecting the rest of the program.
Inheritance: Inheritance allows for one class to inherit the properties and methods of
another class. This allows for code reuse and a clear hierarchy of classes in the program.
Examples of programming languages that support object-oriented programming include Java, C++,
C#, Python, and Ruby, and it's widely used in many domains such as web development, game
development, and enterprise software development.
Structured programming and procedural programming are similar in that both are programming
paradigms that emphasize the use of procedures and control flow statements to organize a
program. However, there are some key differences between the two:
Procedural programming is more focused on the procedures themselves, rather than on the
data that the procedures operate on. Programs are written as a series of instructions, in
which the computer follows a set of steps in a linear, sequential manner.
Structured programming enforces a logical and clear structure in the program and it's
advocated to make the program more understandable and easy to maintain.
In procedural programming, the focus is on the procedures themselves, rather than on the
data that the procedures operate on. Programs are written as a series of instructions, in
which the computer follows a set of steps in a linear, sequential manner.
It's worth noting that both paradigms are still widely used today, and many modern programming
languages have support for both procedural and structured programming. However, OOP paradigm
is considered as more modern and widely used among new development projects.
Here are the key differences between structured and procedural programming, summarized in a
point-wise format:
Structured Programming:
Emphasizes the use of well-defined structures, such as functions and control flow
statements, to organize a program.
Focuses on the organization of the code and making it more readable, maintainable, and
less prone to errors.
Procedural Programming:
Focuses on the procedures themselves, rather than on the data that the procedures operate
on.
Programs are written as a series of instructions, in which the computer follows a set of steps
in a linear, sequential manner.
The focus is on the procedures themselves, rather than on the data that the procedures
operate on.
Classes: A blueprint for creating objects (a particular data structure), providing initial values for
state (member variables or attributes), and implementations of behavior (member functions or
methods).
Objects: An instance of a class. It has the state and behavior defined by the class.
Methods: Functions that belong to a class and operate on objects of that class.
Message passing: The process of sending a request to an object, triggering the execution of one of
its methods. In object-oriented programming, it is the mechanism of communicating between
objects, allowing objects to send and receive messages.
Inheritance: A mechanism that allows a new class to inherit properties and behavior from an
existing class, creating a hierarchy of classes that can share attributes and methods. In Python, a
subclass can inherit from a superclass using the syntax "class SubClass(SuperClass):".
Containership:
"Containership" in Python refers to the concept of containerization. This is the process of packaging
code and its dependencies into a single unit, called a container ,that can be run in any environment
without changes. The main goal of containerization is to ensure that an application works
consistently across different environments and systems, making it easier to deploy, test, and
manage.
The most popular tool for containerizing applications in Python is Docker. With Docker, you can
create a Docker image of your application, which includes everything that your code needs to run,
such as libraries, runtime environment, and other dependencies. Docker images can then be run as
containers on any system that has Docker installed, making it easy to deploy and run your
application in different environments.
Docker is a popular tool for containerizing applications, and it can be used with Python as well. With
Docker, you can package your Python application and its dependencies into a single unit, called a
Docker image, which can be run in any environment without changes. This makes it easier to
deploy, test, and manage your application in different environments.
Here's a basic example of how you can use Docker with Python:
1. Write your Python code and specify its dependencies in a requirements.txt file.
2. Create a Dockerfile that defines the environment in which your Python application will run.
This includes the base image, such as a lightweight Linux distribution, and the installation of
the necessary packages and dependencies.
3. Build your Docker image by running the following command in the terminal:
4. Once the image is built, you can run it as a container using the following command:
This will run your Python application inside a container and map port 4000 on your host to port 80
in the container.
Containment or Composition: A relationship between two classes where one class contains the
other class as a member. The contained class is referred to as a member object. In Python,
containment can be implemented using instance variables that hold reference to other objects.
Reusability: The ability to use code written for a class in multiple projects, reducing the need to
rewrite the same code. In Python, reusability is achieved through inheritance, encapsulation, and
abstraction.
Delegation: A relationship between two classes where one class delegates a task to another class.
In Python, delegation can be implemented by calling methods of another class from within a
method of the delegating class.
Delegation is a design pattern that involves creating a wrapper class that delegates method calls to
an underlying object. This can be useful in a variety of situations, including implementing proxies,
adding functionality to existing objects, or creating composable objects.
2. Structural patterns: concern class and object composition. Structural class-level patterns use
inheritance to compose interfaces or implementations.
5. Architectural patterns: deal with large-scale software architecture and high-level design
structures that address various issues such as security, performance, scalability, and
maintainability.
Examples of popular design patterns include Singleton, Factory Method, Observer, Decorator, and
MVC (Model-View-Controller).
Data Abstraction: The process of hiding the implementation details of a class from the user, and
presenting only the necessary information. In Python, data abstraction can be achieved through
encapsulation and the use of accessor and mutator methods (getters and setters).
Encapsulation: The process of wrapping data and functions that manipulate that data within a
single unit, or object. In Python, encapsulation is achieved through the use of classes, which define
objects that contain both data and behavior.
Part-2: classes and objects, class method and self object, class variables and object
variables, public and private members, class methods.
Classes and Objects: A class is a blueprint for creating objects, which are instances of the class.
Classes define the properties and behavior of objects, while objects represent unique instances of
the class. In Python, a class is defined using the keyword "class".
Class: The class is a user-defined data structure that binds the data members and methods
into a single unit. Class is a blueprint or code template for object creation. Using a class,
you can create as many objects as you want.
In this example, we are creating a Person Class with name, sex, and profession instance
variables.
class Person:
def __init__(self, name, gender, profession):
# data members (instance/object variables)
self.name = name
self. gender = gender
self.profession = profession
In Python, Object creation is divided into two parts in Object Creation and Object
initialization
Output:
Name: Shriram Gender: Female Profession: Software Engineer
Shriram working as a Software Engineer
Constructor:
A constructor is a special method that is called when an object is created. It is used to initialize the
object and set its initial state. In Python, the constructor method is called __init__().
There are two types of constructors in Python:
1. Default constructor: A default constructor is a constructor that doesn't take any arguments.
It is automatically called when an object is created. It doesn't do anything by default, but
you can define your own default constructor if you need to initialize some attributes with
default values.
Class Attributes:
When we design a class, we use instance variables and class variables.
In Class, attributes can be defined into two parts:
Instance variables: The instance variables are attributes attached to an instance of a
class. We define instance variables in the constructor ( the __init__() method of a
class).
Class Variables: A class variable is a variable that is declared inside of class, but outside
of any instance method or __init__() method.
Objects do not share instance attributes. Instead, every object has its copy of the instance
attribute and is unique to each object.
All instances of a class share the class variables. However, unlike instance variables, the value
of a class variable is not varied from object to object.
Only one copy of the static variable will be created and shared between all objects of the
class.
A class variable is declared inside of class, but outside of any instance method
or __init__() method.
By convention, typically it is placed right below the class header and before the constructor
method and other methods.
class Student:
# Class variable
school_name = 'ABC School '
Output:
Emma 10 ABC School
Jessa 20 ABC School
List the differences between class variables and object variables python:
Class variables and object variables in Python are used to store data associated with classes
and objects, respectively. Here are the main differences between the two:
1. Scope: Class variables are defined inside a class and are shared by all instances
(objects) of the class. Object variables, on the other hand, are unique to each object
and are only accessible through that particular object.
2. Declaration: Class variables are declared using the "class" keyword, whereas object
variables are created for each object by using the "self" keyword within a class
method.
3. Initialization: Class variables are typically initialized within the class definition,
whereas object variables are usually initialized within a method of the class and can
have different values for different objects.
4. Modification: Class variables can be modified by any instance of the class and the
change will be reflected in all other instances. Object variables can only be modified
through the specific object they belong to.
5. Purpose: Class variables are used to store information that is common to all objects
of a class, while object variables are used to store information that is specific to each
individual object.
Class Variables and Object Variables: Class variables are shared among all instances of a
class and are defined within the class, but outside of any method. Object variables are unique
to each instance of a class and are defined within methods.
Class Methods:
Instance method: Used to access or modify the object state. If we use instance
variables inside a method, such methods are called instance methods. It must have
a self parameter to refer to the current object.
Class method: Used to access or modify the class state. In method implementation, if
we use only class variables, then such type of methods we should declare as a class
method. The class method has a cls parameter which refers to the class.
Static method: It is a general utility method that performs a task in isolation. Inside
this method, we don’t use instance or class variable because this static method
doesn’t take any parameters like self and cls.
Instance methods are methods that are defined inside a class and are called on an
instance of that class. These methods have access to the instance variables of the
class, which means they can operate on the state of the object.
Instance methods are defined using the def keyword and take self as the first
argument, which refers to the instance of the class on which the method is called.
The self argument allows the instance method to access and modify the instance
variables of the class.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def say_hello(self):
print("Hello, my name is", self.name, "and I am", self.age, "years old.")
In this example, we have defined a class called Person with two instance variables name and
age. We have also defined an instance method called say_hello() which takes self as the first
argument and prints a message that includes the name and age of the person.
To create an object of this class and call the say_hello() method, we can do the following:
p = Person("Shriram", 25)
p.say_hello()
Example1:
class Person:
count = 0
@classmethod
def get_count(cls):
return cls.count
In this example, we have defined a class called Person with two instance variables name and
age, and a class variable count. The __init__() method initializes the name and age instance
variables and increments the count class variable by 1 for each new instance of the class.
We have also defined a class method called get_count() which takes the class as the first
argument, conventionally named cls, and returns the value of the count class variable.
To call the get_count() method on the Person class, we can do the following:
print(Person.get_count())
This will print the value of the count class variable, which is the total number of instances of
the Person class that have been created. We can also call the get_count() method on any
instance of the Person class, like this:
p1 = Person("Alice", 25)
p2 = Person("Bob", 30)
print(p1.get_count())
print(p2.get_count())
This will print the same value of the count class variable for both instances, which is the total
number of instances of the Person class that have been created.
Example 2:
from datetime import date
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def calculate_age(cls, name, birth_year):
# calculate age an set it as a age
# return new object
return cls(name, date.today().year - birth_year)
def show(self):
print(self.name + "'s age is: " + str(self.age))
Class Method and Self Object: The "self" object is a reference to the instance of a class. It is used to
access the attributes and methods of the class. A class method is a method that is bound to the
class and not the instance of the class. It is defined using the "@classmethod" decorator.
Public and Private Members: In Python, members with a double underscore prefix (e.g. "__private")
are considered private and are not accessible from outside the class. Members without the prefix
(e.g. "public") are considered public and are accessible from outside the class.
The members of a class that are declared public are easily accessible from any part of the
program. All data members and member functions of a class are public by default.
class Student:
# constructor
def __init__(self, name, age):
obj.displayAge()
Output:
AJAY
20
Protected Access Modifier:
The members of a class that are declared protected are only accessible to a class derived
from it. Data members of a class are declared protected by adding a single underscore ‘_’
symbol before the data member of that class.
# super class
class Student:
# constructor
def __init__(self, name, roll, branch):
self._name = name
self._roll = roll
self._branch = branch
# derived class
class Student(Student):
# constructor
def __init__(self, name, roll, branch):
Student.__init__(self, name, roll, branch)
Output:
Name: R2J
Roll: 1706256
Branch: Information Technology
class Student:
# private members
__name = None
__roll = None
__branch = None
# constructor
def __init__(self, name, roll, branch):
self.__name = name
self.__roll = roll
self.__branch = branch
# creating object
obj = Student("R2J", 1706256, "Information Technology")
Example:
class MyClass:
def __init__(self):
self.public_member = "I am public"
self.__private_member = "I am private"
def get_private(self):
return self.__private_member
Inheritance:
Inheritance is a feature of object-oriented programming that allows one class to inherit the
attributes and methods of another class. In Python, there are two main types of inheritance:
1. Single inheritance: A child class inherits from a single parent class. The child class can access
all the attributes and methods of the parent class.
2. Multiple inheritance: A child class inherits from two or more parent classes. The child class
can access all the attributes and methods of all the parent classes.
Inheritance offers several benefits in Python:
1. Code reusability: Inheritance allows you to reuse code from an existing class, instead of
writing the same code again in a new class.
2. Modularity: Inheritance helps to break down complex problems into smaller, more
manageable parts. You can create separate classes for each part of the problem, and then
use inheritance to combine them.
3. Polymorphism: Inheritance allows you to create multiple classes that share the same
attributes and methods, but behave differently. This is known as polymorphism and is a key
feature of object-oriented programming.
4. Flexibility: Inheritance allows you to easily modify or extend the functionality of an existing
class. You can create a child class that inherits from a parent class and then add or modify
methods or attributes as needed.
Example: Define a class called Vehicle with attributes make and model. Create a child class called
Car that inherits from the Vehicle class and has an additional attribute called year. Create an
object of the Car class and print the make, model, and year of the car.
class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model
class Car(Vehicle):
def __init__(self, make, model, year):
super().__init__(make, model)
self.year = year
In this implementation, we define a Vehicle class with two attributes: make and model. We then
define a Car class that inherits from the Vehicle class and adds an additional attribute: year.
In the __init__() method of the Car class, we call the __init__() method of the Vehicle class using
the super() function, which initializes the make and model attributes. We then set the year
attribute to the value passed in.
We create an instance of the Car class with the make "Toyota", model "Corolla", and year 2022. We
then print the make, model, and year of the car using the instance variables car.make, car.model,
and car.year. The output will be:
Make: Toyota
Model: Corolla
Year: 2022
Polymorphism:
Polymorphism is a way of writing code that can work with different types of objects. It allows you to
use the same method or function name across different classes, so you can write more generic and
reusable code.
There are two main types of polymorphism:
1. Static (compile-time) polymorphism: This involves using overloaded methods or operators
that can take different types of parameters. The correct version of the method or operator
is selected at compile-time based on the type of the parameters.
2. Dynamic (run-time) polymorphism: This involves using inheritance and method overriding to
provide different implementations of a method in different subclasses. The correct version
of the method is selected at run-time based on the type of the object that is calling the
method.
In Python, there are two main types of polymorphism: method overloading and method overriding.
1. Method overloading: This involves defining multiple methods with the same name in a class,
but with different parameters. When the method is called, the correct version of the
method is chosen based on the number and types of the arguments passed in.
2. Method overriding: This involves creating a method in a subclass with the same name as a
method in the parent class. When the method is called on an instance of the subclass, the
version of the method in the subclass is called instead of the version in the parent class.
The benefits of polymorphism in Python include:
1. Code reuse: Polymorphism allows you to reuse code by creating classes with similar
behavior, but different implementations. This makes it easier to write and maintain code,
since you can use the same code for multiple classes.
2. Flexibility: Polymorphism allows you to write code that can handle different types of objects
without knowing their exact type. This makes your code more flexible and adaptable, and
allows you to write more generic and reusable code.
3. Readability: Polymorphism can make your code more readable by allowing you to use the
same method or function names across different classes, rather than having to create
unique names for each class.
4. Extensibility: Polymorphism allows you to easily extend the behavior of your code by
creating new classes that behave in a similar way to existing classes. This makes it easier to
add new features or functionality to your code, without having to change the existing code.
Example of method overriding:
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * (self.radius ** 2)
r = Rectangle(10, 5)
c = Circle(7)
print(r.area()) # 50
print(c.area()) # 153.86 (approx.)
In this example, we have a Shape class that defines a generic area method that does nothing. We
then create two subclasses, Rectangle and Circle, that inherit from Shape and provide their own
implementation of the area method.
The Rectangle class takes a width and height as arguments in its constructor and calculates the
area based on those values. The Circle class takes a radius as an argument in its constructor and
calculates the area using the formula for the area of a circle.
When we create objects of these classes and call their area method, the correct implementation is
called based on the type of the object. This is an example of dynamic (run-time) polymorphism,
which allows us to write more flexible and reusable code.
class MyClass:
def my_method(self, arg1, arg2=None, *args, **kwargs):
if arg2 is not None:
# Do something with arg1 and arg2
else:
# Do something with just arg1
if args:
# Do something with additional arguments
if kwargs:
# Do something with keyword arguments
In this example, the my_method method can be called with one or two arguments. If it is called
with two arguments, it will do one thing, and if it is called with just one argument, it will do
something else. Additionally, it can accept any number of additional arguments using *args and
**kwargs, which allows it to be more flexible.
Here's an example of how you might call the my_method method:
obj = MyClass()
obj.my_method(1)
obj.my_method(1, 2)
obj.my_method(1, 2, 3, 4, foo='bar')
In the first call, only one argument is passed, so the method will do something with just that
argument. In the second call, two arguments are passed, so the method will do something different.
In the third call, additional arguments and keyword arguments are passed, which the method can
handle using *args and **kwargs.
Example: Define a function called total_area() that takes a list of shapes as an argument
and returns the total area of all the shapes. The shapes can be either Circle or Rectangle
objects. Create objects of both classes and pass them to the total_area() function.
import math
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def total_area(shapes):
area = 0
for shape in shapes:
area += shape.area()
return area
n this example, we define two classes Circle and Rectangle, each with their own area() method to
calculate the area of the shape. The total_area() function takes a list of shapes as an argument, and
uses a loop to iterate over each shape in the list and add its area to a running total. The function
then returns the total area of all the shapes.
We create objects of both Circle and Rectangle classes, and pass them as a list to the total_area()
function to calculate the total area of all the shapes. This approach allows us to work with objects of
different classes in a single function.
Example: Define a class called Student with attributes name, age, and grade. Create a
method in the class that calculates the student's grade point average (GPA). Create an
object of the class and print the student's name, age, and GPA.
class Student:
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade
def calculate_gpa(self):
total = sum(self.grade)
gpa = total / len(self.grade)
return gpa
In this example, we define a Student class with attributes name, age, and grade. The
calculate_gpa() method calculates the student's grade point average by summing the grades and
dividing by the number of grades.
We create an object s of the Student class with the student's name, age, and a list of grades. We
then print the student's name, age, and GPA by calling the calculate_gpa() method.
Exapmle: Define a class called Employee with attributes name, age, and salary. Create an
object of the class and print the employee's name, age, and salary.
class Employee:
def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
In this example, we define a Employee class with attributes name, age, and salary. The
__init__() method initializes the object with these attributes.
We create an object emp of the Employee class with the employee's name, age, and
salary. We then print the employee's name, age, and salary by accessing these attributes
using the object.
Example: Define a class called Rectangle with attributes length and width. Create an
object of the class and calculate the area of the rectangle.
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
In this example, we define a Rectangle class with attributes length and width. The
__init__() method initializes the object with these attributes. We also define an area()
method which calculates and returns the area of the rectangle.
We create an object rect of the Rectangle class with length 5 and width 10. We then call
the area() method of the object rect to calculate the area of the rectangle and store it in a
variable area. Finally, we print the area of the rectangle by printing the value of the variable
area.
Example: Write a python program to create class car with two attributes name &
cost. Create two objects and display information.
class Car:
def __init__(self, name, cost):
self.name = name
self.cost = cost