0% found this document useful (0 votes)
40 views105 pages

OOP Notes

Uploaded by

hondomat
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views105 pages

OOP Notes

Uploaded by

hondomat
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 105

Object Oriented

Programming Using
Python

1
Index

1. Introduction to Object Oriented Programming in


Python

2. Difference between object and procedural

oriented programming
3. What are Classes and Objects?
4. Object-Oriented Programming methodologies:
• Inheritance
• Polymorphism
• Encapsulation
• Abstraction
2
1. Introduction to Object Oriented
Programming in Python

Object Oriented Programming is a way of


computer programming using the idea of
“objects” to represents data and methods. It
is also, an approach used for creating neat
and reusable code instead of a redundant
one.

3
2. Difference between Object Oriented and
Procedural Oriented Programming
Procedural-Oriented
Object-Oriented Programming (OOP)
Programming
(Pop)
It is a bottom-up approach It is a top-down approach

Program is divided into objects Program is divided into functions

Makes use of Access modifiers


Doesn’t use Access modifiers
‘public’, private’, protected’

It is more secure It is less secure

Object can move freely within member Data can move freely from function to
functions function within programs

It supports inheritance It does not support inheritance


3. What are Classes and Objects?
A class is a collection of objects or you
blueprint of objects defining thecancommon
say it is a
attributes and

•Class is defined under a “Class” Keyword.

•Example:
class class1(): // class 1 is the name of the class

5
Creating an Object and Class in python:

Example:

class employee():
def init (self,name,age,id,salary): //creating a
function self.name = name // self is an instance of a class
self.age = age
self.salary = salary
self.id = id

emp1 = employee("harshit",22,1000,1234) //creating objects


emp2 = employee("arjun",23,2000,2234)
print(emp1. dict )//Prints dictionary

6
4. Object-Oriented
Programming
methodologies:

❑ Inheritance
❑ Polymorphism
❑ Encapsulation
❑ Abstraction

7
Inheritance:
• Ever heard of this dialogue from relatives “you look exactly
like your father/mother” the reason behind this is called
‘inheritance’.
• From the Programming aspect, It generally means
“inheriting or transfer of characteristics from parent to
child class without any modification”.
• The new class is called the derived/child class and the one
from which it is derived is called a parent/base class.
9
Single Inheritance:

Single level inheritance enables a derived


class to inherit characteristics from a single parent class.
Example:

class employee1()://This is a parent class


def init (self, name, age, salary):
self.name = name
self.age = age
self.salary = salary

class childemployee(employee1)://This is a child class


def init (self, name, age, salary,id):
self.name = name
self.age = age
self.salary = salary
self.id = id
emp1 = employee1('harshit',22,1000)
print(emp1.age)

Output: 22
Multilevel Inheritance:

Multi-level inheritance enables a derived class to inherit properties from an


immediate parent class which in turn inherits properties from his parent
class.

Example:

class employee()://Super class


def init
(self,name,age,salary):
self.name = name
self.age = age
self.salary = salary
class childemployee1(employee)://First child class
def init
(self,name,age,salary):
self.name = name
self.age = age
class childemployee2(childemployee1)://Second child class
def init (self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
emp1 = employee('harshit',22,1000)
emp2 = childemployee1('arjun',23,2000)

print(emp1.age)

print(emp2.age)

Output: 22,23
Hierarchical Inheritance:

Hierarchical level inheritance enables more than one


derived
class to inherit properties from a parent class.

Example:

class employee():
def init (self, name, age, salary): //Hierarchical
Inheritance self.name = name
self.age = age
self.salary = salary
class childemployee1(employee):
def init
(self,name,age,salary):
self.name = name
self.age = age
self.salary = salary

class childemployee2(employee):
def init (self, name,
age, salary): self.name = name
self.age = age
self.salary = salary
emp1 = employee('harshit',22,1000)
emp2 = employee('arjun',23,2000)
Multiple Inheritance:

Multiple level inheritance enables one derived class to


inherit properties from more than one base class.

Example:

class employee1(): //Parent class


def init (self, name, age,
salary): self.name = name
self.age = age
self.salary = salary
class employee2(): //Parent class
def init (self,name,age,salary,id):
self.name = name
self.age = age
self.salary = salary
self.id = id

class childemployee(employee1,employee2):
def init (self, name, age, salary,id):
self.name = name
self.age = age
self.salary = salary
self.id = id

emp1 = employee1('harshit',22,1000)
emp2 = employee2('arjun',23,2000,1234)
Polymorphism:
• You all must have used GPS for navigating the route, Isn’t it
amazing how many different routes you come across for the
same destination depending on the traffic, from a
programming point of view this is called ‘polymorphism’.
• It is one such OOP methodology where one task can be
performed in several different ways.
• To put it in simple words, it is a property of an object which
allows it to take multiple forms.
Polymorphism is of two types:

❑ Compile-time Polymorphism
❑ Run-time Polymorphism
Compile-time Polymorphism:
A compile-time polymorphism also called as static
polymorphism which gets resolved during the compilation
time of the program. One common example is “method
overloading”
Example:

class employee1():
def
name(self):
print("Harshit is his name")
def salary(self):
print("3000 is his salary")
def age(self):
print("22 is his age")

class employee2():
def name(self):
print("Rahul is his name")
def salary(self):
print("4000 is his salary")
def age(self):
print("23 is his age")
def func(obj)://Method Overloading
obj.name()
obj.salary()
obj.age()

obj_emp1 = employee1()
obj_emp2 = employee2()
func(obj_emp1)
func(obj_emp2)

Output:

Harshit is his
name 3000 is his
salary 22 is his
age
Rahul is his name
23
4000 is his
Run-time Polymorphism:
A run-time Polymorphism is also, called as dynamic
polymorphism where it gets resolved into the run time. One
common example of Run-time polymorphism is “method
overriding”.
Example:

class employee():
def init
(self,name,age,id,salary):
self.name = name
self.age = age
self.salary = salary
self.id = id
def earn(self):
pass

class childemployee1(employee):
def earn(self): //Run-time polymorphism
print("no money")
class childemployee2(employee):
def earn(self):
print("has money")

c = childemployee1
c.earn(employee)
d=
childemployee2
d.earn(employee)

Output: no money, has


money
Abstraction:
• Suppose you booked a movie ticket from bookmyshow
using net banking or any other process.
• You don’t know the procedure of how the pin is generated
or how the verification is done.
• This is called ‘abstraction’ from the programming aspect, it
basically means you only show the implementation details
of a particular process and hide the details from the user.
Class method vs static method in Python

• The @classmethod decorator is a built-in function decorator that is an


expression that gets evaluated after your function is defined.
• The result of that evaluation shadows your function definition.
• A class method receives the class as an implicit first argument, just
like an instance method receives the instance
Syntax Python Class Method:
• class C(object):
@classmethod
def fun(cls, arg1, arg2, ...):
....
• fun: function that needs to be converted into a class method
• returns: a class method for function.
• A class method is a method that is bound to the class and not the
object of the class.
• They have the access to the state of the class as it takes a class
parameter that points to the class and not the object instance.
• It can modify a class state that would apply across all the instances of
the class. For example, it can modify a class variable that will be
applicable to all the instances.
What is the Static Method in Python?

• A static method does not receive an implicit first argument.


• A static method is also a method that is bound to the class and not
the object of the class.
• This method can’t access or modify the class state.
• It is present in a class because it makes sense for the method to be
present in class.
Syntax Python Static Method:
• class C(object):
• @staticmethod
• def fun(arg1, arg2, ...):
• ...
• returns: a static method for function fun.
Class method vs Static Method
The difference between the Class method and the static method is:

• A class method takes cls as the first parameter while a static method needs
no specific parameters.
• A class method can access or modify the class state while a static method
can’t access or modify it.
• In general, static methods know nothing about the class state. They are
utility-type methods that take some parameters and work upon those
parameters. On the other hand class methods must have class as a
parameter.
• We use @classmethod decorator in python to create a class method and
we use @staticmethod decorator to create a static method in python.
# Python program to demonstrate
# use of class method and static method.
from datetime import date

class Person:
def __init__(self, name, age):
self.name = name
self.age = age

# a class method to create a Person object by birth year.


@classmethod
def fromBirthYear(cls, name, year):
return cls(name, date.today().year - year)

# a static method to check if a Person is adult or not.


@staticmethod
def isAdult(age):
return age > 18

person1 = Person('mayank', 21)


person2 = Person.fromBirthYear('mayank', 1996)

print(person1.age)
print(person2.age)

# print the result


print(Person.isAdult(22))
Python Property Decorator – @property

• A decorator feature in Python wraps in a function, appends several


functionalities to existing code and then returns it.
• Methods and functions are known to be callable as they can be called.
• Therefore, a decorator is also a callable that returns callable.
• This is also known as metaprogramming as at compile time a section
of program alters another section of the program.
Python @property decorator

• @property decorator is a built-in decorator in Python which is helpful


in defining the properties effortlessly without manually calling the
inbuilt function property().
• Which is used to return the property attributes of a class from the
stated getter, setter and deleter as parameters.
# Python program to illustrate the use of
# @property decorator

# Defining class
class Portal:

# Defining __init__ method


def __init__(self):
self.__name =''

# Using @property decorator


@property

# Getter method
def name(self):
return self.__name

# Setter method
@name.setter
def name(self, val):
self.__name = val

# Deleter method
@name.deleter
def name(self):
del self.__name

# Creating object
p = Portal();

# Setting name
p.name = ‘Hi DR FURUSA'

# Prints name
print (p.name)

# Deletes name
del p.name

# As name is deleted above this


# will throw an error
print (p.name)
• Here, the @property decorator is used to define the property name in
the class Portal, that has three methods(getter, setter, and deleter)
with similar names i.e, name(), but they have different number of
parameters.
• Where, the method name(self) labeled with @property is a getter
method, name(self, val) is a setter method as it is used to set the
value of the attribute __name and so its labeled with @name.setter.
• Lastly, the method labeled with @name.deleter is a deleter method
which can delete the assigned value by the setter method.
• However, deleter is invoked with the help of a keyword del.
What is Composition (Has-A Relation)?

• It is one of the fundamental concepts of Object-Oriented Programming.


• In this concept, we will describe a class that references to one or more
objects of other classes as an Instance variable.
• Here, by using the class name or by creating the object we can access the
members of one class inside another class.
• It enables creating complex types by combining objects of different classes.
• It means that a class Composite can contain an object of another class
Component.
• This type of relationship is known as Has-A Relation.
class Component:

# composite class constructor


def __init__(self):
print('Component class object created...')

# composite class instance method


def m1(self):
print('Component class m1() method executed...')

class Composite:

# composite class constructor


def __init__(self):

# creating object of component class


self.obj1 = Component()

print('Composite class object also created...')

# composite class instance method


def m2(self):

print('Composite class m2() method executed...')

# calling m1() method of component class


self.obj1.m1()

# creating object of composite class


obj2 = Composite()

# calling m2() method of composite class


obj2.m2()
Explanation
• In the above example, we created two classes Composite and Component to
show the Has-A Relation among them.
• In the Component class, we have one constructor and an instance method m1().
• Similarly, in Composite class, we have one constructor in which we created an
object of Component Class. Whenever we create an object of Composite Class,
the object of the Component class is automatically created.
• Now in m2() method of Composite class we are calling m1() method of
Component Class using instance variable obj1 in which reference of Component
Class is stored.
• Now, whenever we call m2() method of Composite Class, automatically m1()
method of Component Class will be called.
Composition vs Inheritance

• It’s big confusing among most of the people that both the concepts are pointing to
Code Reusability then what is the difference b/w Inheritance and Composition and
when to use Inheritance and when to use Composition?

• Inheritance is used where a class wants to derive the nature of parent class and
then modify or extend the functionality of it.
• Inheritance will extend the functionality with extra features allows overriding of
methods, but in the case of Composition, we can only use that class we can not
modify or extend the functionality of it. It will not provide extra features.
• Thus, when one needs to use the class as it without any modification, the
composition is recommended and when one needs to change the behavior of the
method in another class, then inheritance is recommended.
Python’s Magic Methods Guide
• To get the list of magic functions in Python, open cmd or terminal,
type python3 to go to the Python console, and type:
• dir(int)
Python’s Magic Methods Guide
Initialization and Construction

• __new__: To get called in an object’s instantiation.


• __init__: To get called by the __new__ method.
• __del__: It is the destructor.
Numeric magic methods

• __trunc__(self): Implements behavior for math.trunc()


• __ceil__(self): Implements behavior for math.ceil()
• __floor__(self): Implements behavior for math.floor()
• __round__(self,n): Implements behavior for the built-in round()
• __invert__(self): Implements behavior for inversion using the ~ operator.
• __abs__(self): Implements behavior for the built-in abs()
• __neg__(self): Implements behavior for negation
• __pos__(self): Implements behavior for unary positive
Arithmetic operators
• __add__(self, other): Implements behavior for math.trunc()
• __sub__(self, other): Implements behavior for math.ceil()
• __mul__(self, other): Implements behavior for math.floor()
• __floordiv__(self, other): Implements behavior for the built-in round()
• __div__(self, other): Implements behavior for inversion using the ~ operator.
• __truediv__(self, other): Implements behavior for the built-in abs()
• __mod__(self, other): Implements behavior for negation
• __divmod__(self, other): Implements behavior for unary positive
• __pow__: Implements behavior for exponents using the ** operator.
• __lshift__(self, other): Implements left bitwise shift using the << operator.
• __rshift__(self, other): Implements right bitwise shift using the >> operator.
• __and__(self, other): Implements bitwise and using the & operator.
• __or__(self, other): Implements bitwise or using the | operator.
• __xor__(self, other): Implements bitwise xor using the ^ operator.
String Magic Methods
• __str__(self): Defines behavior for when str() is called on an instance of your class.
• __repr__(self): To get called by built-int repr() method to return a machine readable
representation of a type.
• __unicode__(self): This method to return an unicode string of a type.
• __format__(self, formatstr): return a new style of string.
• __hash__(self): It has to return an integer, and its result is used for quick key
comparison in dictionaries.
• __nonzero__(self): Defines behavior for when bool() is called on an instance of your
class.
• __dir__(self): This method to return a list of attributes of a class.
• __sizeof__(self): It return the size of the object.
Comparison magic methods
• __eq__(self, other): Defines behavior for the equality operator, ==.
• __ne__(self, other): Defines behavior for the inequality operator, !=.
• __lt__(self, other): Defines behavior for the less-than operator, <.
• __gt__(self, other): Defines behavior for the greater-than operator, >.
• __le__(self, other): Defines behavior for the less-than-or-equal-to
operator, <=.
• __ge__(self, other): Defines behavior for the greater-than-or-equal-to
operator, >=.
Example of Dunder or Magic Methods in Python

• __init__ method
• The __init__ method for initialization is invoked without any call,
when an instance of a class is created, like constructors in certain
other programming languages such as C++, Java, C#, PHP, etc.
• These methods are the reason we can add two strings with the ‘+’
operator without any explicit typecasting.
Code
• # declare our own string class
• class String:

• # magic method to initiate object
• def __init__(self, string):
• self.string = string

• # Driver Code
• if __name__ == '__main__':

• # object creation
• string1 = String('Hello')

• # print object location


• print(string1)
__repr__ method
• # declare our own string class
• class String:

• # magic method to initiate object
• def __init__(self, string):
• self.string = string

• # print our string object
• def __repr__(self):
• return 'Object: {}'.format(self.string)

• # Driver Code
• if __name__ == '__main__':

• # object creation
• string1 = String('Hello')

• # print object location


• print(string1)
Design Patterns in Python

• Design Patterns is the most essential part of Software Engineering, as


they provide the general repeatable solution to a commonly occurring
problem in software design.
• They usually represent some of the best practices adopted by
experienced object-oriented software developers.
• We can not consider the Design Patterns as the finished design that
can be directly converted into code.
• They are only templates that describe how to solve a particular
problem with great efficiency.
Classification of Design Patterns
Creational Design Pattern:

• Creational patterns provides essential information regarding the Class


instantiation or the object instantiation.
• Class Creational Pattern and the Object Creational pattern is the
major categorization of the Creational Design Patterns.
• While class-creation patterns use inheritance effectively in the
instantiation process, object-creation patterns use delegation
effectively to get the job done.
Classification of Creational Design Patterns –
• Factory Method
• Abstract Factory Method
• Builder Method
• Prototype Method
• Singleton Method
Structural Design Patterns:

• Structural design patterns are about organizing different classes and


objects to form larger structures and provide new functionality while
keeping these structures flexible and efficient.
• Mostly they use Inheritance to compose all the interfaces.
• It also identifies the relationships which led to the simplification of
the structure.
Classification of Structural Design Patterns –

• Adapter Method
• Bridge Method
• Composite Method
• Decorator Method
• Facade Method
• Proxy Method
• FlyWeight Method
Behavioral Design Pattern:

• Behavioral patterns are all about identifying the common


communication patterns between objects and realize these patterns.
• These patterns are concerned with algorithms and the assignment of
responsibilities between objects.
Classification of Behavioral Design Patterns –

• Chain of Responsibility Method


• Command Method
• Iterator Method
• Mediator Method
• Memento Method
• Observer Method
• State Method
• Strategy Method
• Template Method
• Visitor Method
Advantages of using Design Patterns

• Reusability: By using Inheritance, they helps in making the code reusable and
hence we can use them in multiple projects.
• Transparent: It improves the transparency of the code for all the developers who
are going to use it in future.
• Established Solution: We can blindly believe on the solution provided by Design
Patterns as they are well-proved and testified at critical stages.
• Established Communication: Design patterns make communication between
designers more efficient. Software professionals can immediately picture the high-
level design in their heads when they refer the name of the pattern used to solve a
particular issue when discussing system design.
• Efficient Development: Design patterns helps in the development of the highly
cohesive modules with minimal coupling.
Designing Classes for Data Handling
A python code using a class to read data from
a csv
• import pandas as pd

• class CSVReader:
• def __init__(self, file_path):
• self.file_path = file_path
• self.data = None

• def read_data(self):
• self.data = pd.read_csv(self.file_path)

• def display_data(self, n=5):


• if self.data is not None:
• print(self.data.head(n))
• else:
• print("Data not read yet!")

• # Usage:
• file_path = "C:/Users/HONZ/Desktop/Desktop/IBM/honz.csv"
• reader = CSVReader(file_path)
• reader.read_data()
• reader.display_data(10) # Display first 10 rows
import pandas as pd

class CSVReader:
def __init__(self, file_path):
self.file_path = file_path
self.data = None

def read_data(self):
self.data = pd.read_csv(self.file_path)

def display_data(self, n=5):


if self.data is not None:
print(self.data.head(n))
else:
print("Data not read yet!")

# Usage:
file_path = "your_file_path_here.csv"
reader = CSVReader(file_path)
reader.read_data()
reader.display_data(10) # Display first 10 rows
SOLID Principle in Programming

• In software development, Object-Oriented Design plays a crucial role


when it comes to writing flexible, scalable, maintainable, and reusable
code.
• There are so many benefits of using OOD but every developer should
also have the knowledge of the SOLID principle for good object-
oriented design in programming.
• The SOLID principle was introduced by Robert C. Martin, also known
as Uncle Bob and it is a coding standard in programming.
This principle is an acronym of the five
principles which is given below…
• Single Responsibility Principle (SRP)
• Open/Closed Principle
• Liskov’s Substitution Principle (LSP)
• Interface Segregation Principle (ISP)
• Dependency Inversion Principle (DIP)
• The SOLID principle helps in reducing tight coupling.
• Tight coupling means a group of classes are highly dependent on one
another which you should avoid in your code.
• Opposite of tight coupling is loose coupling and your code is
considered as a good code when it has loosely-coupled classes.
• Loosely coupled classes minimize changes in your code, helps in
making code more reusable, maintainable, flexible and stable.
Single Responsibility Principle:
• This principle states that “a class should have only one reason to change” which means
every class should have a single responsibility or single job or single purpose.
• Take the example of developing software.
• The task is divided into different members doing different things as front-end designers
do design, the tester does testing and backend developer takes care of backend
development part then we can say that everyone has a single job or responsibility.
• Most of the time it happens that when programmers have to add features or new
behavior they implement everything into the existing class which is completely wrong. It
makes their code lengthy, complex and consumes time when later something needs to be
modified.
• Use layers in your application and break God classes into smaller classes or modules.
Open/Closed Principle:
• This principle states that “software entities (classes, modules, functions,
etc.) should be open for extension, but closed for modification” which
means you should be able to extend a class behavior, without modifying it.
• Suppose developer A needs to release an update for a library or
framework and developer B wants some modification or add some feature
on that then developer B is allowed to extend the existing class created by
developer A but developer B is not supposed to modify the class directly.
• Using this principle separates the existing code from the modified code so
it provides better stability, maintainability and minimizes changes as in
your code.
Liskov’s Substitution Principle
• The principle was introduced by Barbara Liskov in 1987 and according to
this principle “Derived or child classes must be substitutable for their
base or parent classes“.
• This principle ensures that any class that is the child of a parent class
should be usable in place of its parent without any unexpected behavior.
• You can understand it in a way that a farmer’s son should inherit farming
skills from his father and should be able to replace his father if needed.
• If the son wants to become a farmer then he can replace his father but
if he wants to become a cricketer then definitely the son can’t replace
his father even though they both belong to the same family hierarchy.
• One of the classic examples of this principle is a rectangle having four
sides. A rectangle’s height can be any value and width can be any
value. A square is a rectangle with equal width and height.
• So we can say that we can extend the properties of the rectangle class
into square class.
• In order to do that you need to swap the child (square) class with
parent (rectangle) class to fit the definition of a square having four
equal sides but a derived class does not affect the behavior of the
parent class so if you will do that it will violate the Liskov Substitution
Principle
Interface Segregation Principle
• This principle is the first principle that applies to Interfaces instead of classes in SOLID and it is similar
to the single responsibility principle.
• It states that “do not force any client to implement an interface which is irrelevant to them“. Here
your main goal is to focus on avoiding fat interface and give preference to many small client-specific
interfaces.
• You should prefer many client interfaces rather than one general interface and each interface should
have a specific responsibility.
• Suppose if you enter a restaurant and you are pure vegetarian. The waiter in that restaurant gave
you the menu card which includes vegetarian items, non-vegetarian items, drinks, and sweets. In this
case, as a customer, you should have a menu card which includes only vegetarian items, not
everything which you don’t eat in your food. Here the menu should be different for different types of
customers.
• The common or general menu card for everyone can be divided into multiple cards instead of just
one.
• Using this principle helps in reducing the side effects and frequency of required changes.
Dependency Inversion Principle
• Before we discuss this topic keep in mind that Dependency Inversion and Dependency Injection both are
different concepts. Most of the people get confused about it and consider both are the same. Now two key
points are here to keep in mind about this principle

• High-level modules/classes should not depend on low-level modules/classes. Both should depend upon
abstractions.
• Abstractions should not depend upon details. Details should depend upon abstractions.
• The above lines simply state that if a high module or class will be dependent more on low-level modules or
class then your code would have tight coupling and if you will try to make a change in one class it can break
another class which is risky at the production level. So always try to make classes loosely coupled as much
as you can and you can achieve this through abstraction. The main motive of this principle is decoupling the
dependencies so if class A changes the class B doesn’t need to care or know about the changes.
• You can consider the real-life example of a TV remote battery. Your remote needs a battery but it’s not
dependent on the battery brand. You can use any XYZ brand that you want and it will work. So we can say
that the TV remote is loosely coupled with the brand name. Dependency Inversion makes your code more
reusable.
Error and Exception Handling in
OOP
Different types of exceptions in python:
• SyntaxError: This exception is raised when the interpreter encounters a syntax error in the code, such as a
misspelled keyword, a missing colon, or an unbalanced parenthesis.
• TypeError: This exception is raised when an operation or function is applied to an object of the wrong type,
such as adding a string to an integer.
• NameError: This exception is raised when a variable or function name is not found in the current scope.
• IndexError: This exception is raised when an index is out of range for a list, tuple, or other sequence types.
• KeyError: This exception is raised when a key is not found in a dictionary.
• ValueError: This exception is raised when a function or method is called with an invalid argument or input,
such as trying to convert a string to an integer when the string does not represent a valid integer.
• AttributeError: This exception is raised when an attribute or method is not found on an object, such as
trying to access a non-existent attribute of a class instance.
• IOError: This exception is raised when an I/O operation, such as reading or writing a file, fails due to an
input/output error.
• ZeroDivisionError: This exception is raised when an attempt is made to divide a number by zero.
• ImportError: This exception is raised when an import statement fails to find or load a module.
Difference between Syntax Error and
Exceptions
• Syntax Error: As the name suggests this error is caused by the wrong
syntax in the code. It leads to the termination of the program.
• # initialize the amount variable
• amount = 10000

• # check that You are eligible to
• # purchase Dsa Self Paced or not
• if(amount > 2999)
• print("You are eligible to purchase Dsa Self Paced")
Exceptions
• Exceptions are raised when the program is syntactically correct, but
the code results in an error. This error does not stop the execution of
the program, however, it changes the normal flow of the program.
• # initialize the amount variable
• marks = 10000

• # perform division with 0
• a = marks / 0
• print(a)
Try and Except Statement – Catching Exceptions

• Try and except statements are used to catch and handle exceptions in
Python.
• Statements that can raise exceptions are kept inside the try clause
and the statements that handle the exception are written inside
except clause.
Python program to handle simple runtime error

• #Python 3

• a = [1, 2, 3]
• try:
• print ("Second element = %d" %(a[1]))

• # Throws error since there are only 3 elements in array
• print ("Fourth element = %d" %(a[3]))

• except:
• print ("An error occurred")
• In the above example, the statements that can cause the error are
placed inside the try statement (second print statement in our case).
The second print statement tries to access the fourth element of the
list which is not there and this throws an exception. This exception is
then caught by the except statement.
Catching Specific Exception

• A try statement can have more than one except clause,


to specify handlers for different exceptions.
• Please note that at most one handler will be executed.
For example, we can add IndexError in the above code.
• The general syntax for adding specific exceptions are –
• try:
• # statement(s)
• except IndexError:
• # statement(s)
• except ValueError:
• # statement(s)
# Program to handle multiple errors with one
# except statement
# Python 3

def fun(a):
if a < 4:

# throws ZeroDivisionError for a = 3


b = a/(a-3)

# throws NameError if a >= 4


print("Value of b = ", b)

try:
fun(3)
fun(5)

# note that braces () are necessary here for


# multiple exceptions
except ZeroDivisionError:
print("ZeroDivisionError Occurred and Handled")
except NameError:
print("NameError Occurred and Handled")
Try with Else Clause

• In Python, you can also use the else clause on the try-except block
which must be present after all the except clauses. The code enters
the else block only if the try clause does not raise an exception.

• Example: Try with else clause


• # Program to depict else clause with try-except
• # Python 3
• # Function which returns a/b
• def AbyB(a , b):
• try:
• c = ((a+b) / (a-b))
• except ZeroDivisionError:
• print ("a/b result in 0")
• else:
• print (c)

• # Driver program to test above function


• AbyB(2.0, 3.0)
• AbyB(3.0, 3.0)
Finally Keyword in Python

• Python provides a keyword finally, which is always executed after the


try and except blocks.
• The final block always executes after the normal termination of the
try block or after the try block terminates due to some exception.
Syntax:

• try:
• # Some Code....

• except:
• # optional block
• # Handling of exception (if required)

• else:
• # execute if no exception

• finally:
• # Some code .....(always executed)
Raising Exception

• The raise statement allows the programmer to force a specific


exception to occur.
• The sole argument in raise indicates the exception to be raised.
• This must be either an exception instance or an exception class (a
class that derives from Exception).
Program to depict Raising Exception

• try:
• raise NameError("Hi there") # Raise Error
• except NameError:
• print ("An exception")
• raise # To determine whether the exception was raised or not
Advantages of Exception Handling:

• Improved program reliability: By handling exceptions properly, you can prevent


your program from crashing or producing incorrect results due to unexpected
errors or input.
• Simplified error handling: Exception handling allows you to separate error
handling code from the main program logic, making it easier to read and
maintain your code.
• Cleaner code: With exception handling, you can avoid using complex
conditional statements to check for errors, leading to cleaner and more
readable code.
• Easier debugging: When an exception is raised, the Python interpreter prints a
traceback that shows the exact location where the exception occurred, making
it easier to debug your code.
Disadvantages of Exception Handling:

• Performance overhead: Exception handling can be slower than using


conditional statements to check for errors, as the interpreter has to
perform additional work to catch and handle the exception.
• Increased code complexity: Exception handling can make your code
more complex, especially if you have to handle multiple types of
exceptions or implement complex error handling logic.
• Possible security risks: Improperly handled exceptions can potentially
reveal sensitive information or create security vulnerabilities in your
code, so it’s important to handle exceptions carefully and avoid
exposing too much information about your program.
Writing Efficient and Maintainable OOP Code in Python

• Writing efficient and maintainable object-oriented programming


(OOP) code in Python involves understanding both the principles of
OOP and best practices in Python programming.
• Here are some guidelines and practices to follow:
Encapsulation:
• Group related attributes/methods in classes.
• Hide internal representation using private attributes/methods (e.g.,
_attr).
• Offer public methods for attribute interaction.
Inheritance:

• Extend functionality of existing classes.


• Be wary of deep inheritance trees; keep them shallow.
Polymorphism:

• Enable methods to work with objects from various classes.


• Use abstract classes to set common interfaces.
Modularity:

• Break down large functionalities into manageable chunks.


• Organize related modules using Python packages.
Optimization:

• Prioritize clear code over early optimization.


• Only optimize after profiling and identifying bottlenecks.
DRY Principle (Don't Repeat Yourself):

• Centralize recurring code patterns.


• Avoid manual copying and pasting of code blocks.
Cohesion and Coupling:

• Ensure each class has a single, clear responsibility.


• Minimize dependencies between classes.
Use of Properties:

• Favor Python properties over traditional getter/setter methods.


• This ensures a clean and Pythonic interface.
Documentation:

• Utilize docstrings for classes and methods.


• Offer clear explanations for the intended functionality.
Magic/Dunder Methods:

• Implement methods like __str__, __eq__, etc., for custom behaviors.


• This makes custom classes more intuitive and Pythonic.
Error Handling:

• Utilize exceptions for unexpected scenarios.


• Avoid employing exceptions for regular control flow.
Unit Testing:

• Consistently test classes and methods.


• Use tools like unittest or pytest for structured testing.

You might also like