Python Material
BCA/BSCIT SEM 5
UNIT 2
OOP using Python
Topic: 1 Handling exceptions
Exception handling in Python is a mechanism used to handle runtime errors (exceptions)
gracefully, preventing the program from crashing abruptly. Instead of the program
halting execution, it allows you to catch the error, handle it, and possibly recover from it
or log it appropriately.
What is an Exception?
An exception is an event that occurs during the execution of a program that disrupts the
normal flow of instructions.
Examples of common exceptions:
ZeroDivisionError – Dividing by zero
ValueError – Passing the wrong value to a function
IndexError – Accessing an invalid list index
KeyError – Accessing a missing key in a dictionary
FileNotFoundError – Trying to open a non-existent file
Syntax of Exception Handling
try:
# Code that might raise an exception
except ExceptionType:
# Code that runs if an exception occurs
else:
# Code that runs if NO exception occurs
finally:
# Code that runs NO MATTER WHAT
Examples:
1. Basic Example
try:
x = 10 / 0
except ZeroDivisionError:
print("You can't divide by zero!")
2. Handling Multiple Exceptions
try:
value = int("abc") # Raises ValueError
except ValueError:
print("Invalid conversion to integer.")
except TypeError:
print("Type mismatch error.")
3. Using else and finally
try:
num = int(input("Enter a number: "))
except ValueError:
print("That's not a valid number.")
else:
print("Number is:", num)
finally:
print("Execution complete.")
4. Catching Multiple Exceptions Together
try:
lst = [1, 2, 3]
print(lst[5]) # IndexError
except (IndexError, ValueError) as e:
print("Caught an error:", e)
5. Custom Exception
You can create your own exception class by extending Exception.
class MyCustomError(Exception):
pass
try:
raise MyCustomError("This is a custom exception")
except MyCustomError as e:
print("Caught custom error:", e)
Task: 1 File Handling
try:
file = open("data.txt", "r")
content = file.read()
print(content)
except FileNotFoundError:
print("File not found. Please check the filename.")
finally:
print("Cleaning up...")
Task: 2 Calculator
try:
a = int(input("Enter first number: "))
b = int(input("Enter second number: "))
result = a / b
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
except ValueError:
print("Error: Please enter valid numbers.")
else:
print("Result is:", result)
finally:
print("Calculation finished.")
Exceptions as a control flow mechanism
Using exceptions as a control flow mechanism means intentionally raising and handling
exceptions to control the logic or flow of execution of a program, rather than using
traditional conditionals like if-else.
Examples of Using Exceptions for Control Flow
Example 1: Dictionary Lookup (Traditional vs Exception)
Traditional approach:
my_dict = {"name": "Anil"}
if "age" in my_dict:
print(my_dict["age"])
else:
print("Age not found")
Exception as control flow:
my_dict = {"name": "Anil"}
try:
print(my_dict["age"])
except KeyError:
print("Age not found")
Example 2: Converting to Integer
Using if:
val = input("Enter number: ")
if val.isdigit():
print(int(val))
else:
print("Invalid number.")
Using exception:
try:
val = int(input("Enter number: "))
print(val)
except ValueError:
print("Invalid number.")
Assertions
Assertions in Python are a debugging aid that test if a condition in your code returns True.
If not, the program will raise an AssertionError with an optional message.
They are mainly used to catch bugs early in development and to ensure that the code
behaves as expected.
An assertion is a statement that asserts or assumes something to be true.
Syntax:
assert condition, optional_message
If the condition evaluates to True, the program continues normally.
If it evaluates to False, the program raises an AssertionError.
Why Use Assertions?
Sanity checks during development.
Validate assumptions about data/state.
Helps catch bugs early.
Example:
def divide(a, b):
assert b != 0, "Denominator cannot be zero"
return a / b
print(divide(10, 2))
print(divide(10, 0)) #AssertionError
Abstract Data Types (ADTs)
An Abstract Data Type (ADT) is a logical description of how data is organized and what
operations can be performed on it — without focusing on how it is implemented.
An ADT defines what a data structure does, not how it does it.
Characteristics of ADTs
Encapsulation: The internal representation is hidden.
Defined operations: Only specific operations are allowed.
Implementation-independent: Can be implemented in various ways.
Examples of ADTs in Python:
ADT Description Real-world analogy
Stack LIFO (Last-In-First-Out) Plates on a stack
Queue FIFO (First-In-First-Out) People in line
List Indexed collection Shopping list
Map (Dict) Key-value pairs Phonebook
Set Unordered unique elements Unique student roll numbers
Example: Stack ADT
Stack ADT Operations:
push(item)
pop()
peek()
is_empty()
size()
Code:
class Stack:
def __init__(self):
self.items = []
def push(self, value):
self.items.append(value)
def pop(self):
assert not self.is_empty(), "Stack is empty"
return self.items.pop()
def peek(self):
assert not self.is_empty(), "Stack is empty"
return self.items[-1]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)
# Usage
s = Stack()
s.push(5)
s.push(10)
print(s.pop())
print(s.peek())
Classes in Python
A class is a blueprint for creating objects (instances). It allows you to model real-world
entities with properties (data) and behaviors (methods).
It defines the attributes (data/variables) and methods (functions) that the object will
have.
Think of a class as a recipe, and objects as the cakes baked from that recipe.
Why Use Classes?
Encapsulation: Bundle data & methods together.
Abstraction: Hide implementation details.
Reusability: Write once, use multiple times.
Inheritance: Extend existing classes.
Class Syntax:
class ClassName:
def __init__(self, attributes):
self.attribute = attributes
def method(self):
pass
Object
An object is an instance of a class.
Once a class is defined, you can create multiple objects from it, each having its own set of
attributes.
Example: Person Class
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self):
return f"My name is {self.name} and I am {self.age} years old."
p1 = Person("Kapil", 30)
print(p1.speak())
Important Concepts
__init__() Constructor
Special method that is called when a new object is created.
Used to initialize object attributes.
def __init__(self, name):
self.name = name
self Keyword
Refers to the current object instance.
It must be the first parameter of methods in class.
self.name = name
Special Methods: __str__, @property
class Rectangle:
def __init__(self, length, width):
self.length = length
self.width = width
@property
def area(self):
return self.length * self.width
def __str__(self):
return f"Rectangle({self.length} x {self.width})"
r = Rectangle(10, 5)
print(r.area)
print(r)
Inheritance
Inheritance is a core concept of Object-Oriented Programming (OOP) that allows one
class (called child or derived class) to inherit the properties and methods of another class
(called parent or base class).
Why Use Inheritance?
Code Reusability: Avoid rewriting common code.
Extendibility: Add or override functionality.
Polymorphism: Common interface for different classes.
Syntax of Inheritance in Python
class Parent:
# parent class code
class Child(Parent):
# child class code (inherits from Parent)
Types of Inheritance in Python
Type Description
Single Inheritance One child class inherits from one parent
Multiple Inheritance One child inherits from multiple parents
Multilevel Inheritance Child inherits from a parent, which itself inherits
Hierarchical Inheritance Multiple children inherit from one parent
Hybrid Inheritance Combination of two or more types above
1. Single Inheritance
A child class inherits from one parent class.
Diagram:
Parent
Child
Example (Single Inheritance)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def show(self):
print(f"Name: {self.name}, Age: {self.age}")
class Student(Person): # Inherits from Person
def __init__(self, name, age, student_id):
super().__init__(name, age) # Call parent constructor
self.student_id = student_id
def show_student(self):
self.show()
print(f"Student ID: {self.student_id}")
s1 = Student("Ravi", 20, "ST101")
s1.show_student()
2. Multilevel Inheritance
A class is derived from a class that is already derived from another class.
Diagram:
Grandparent
Parent
Child
Example (Multilevel Inheritance)
class Animal:
def sound(self):
print("Animal makes a sound")
class Dog(Animal):
def sound(self):
print("Dog barks")
class Lion(Dog):
def sound(self):
print("Lion Roars")
l = Lion()
l.sound()
3. Multiple Inheritance
A child class inherits from more than one parent class.
Diagram:
Parent1 Parent2
\ /
Child
Example (Multiple Inheritance)
class Father:
def skills(self):
print("Gardening, Carpentry")
class Mother:
def skills(self):
print("Cooking, Art")
class Child(Father, Mother):
def skills(self):
Father.skills(self)
Mother.skills(self)
print("Python Programming")
c = Child()
c.skills()
4. Hierarchical Inheritance
Multiple child classes inherit from a single parent class.
Diagram:
Parent
/ \
Child1 Child2
Example (Hierarchical Inheritance)
class Vehicle:
def start(self):
print("Vehicle starting...")
class Car(Vehicle):
def drive(self):
print("Driving a car")
class Bike(Vehicle):
def ride(self):
print("Riding a bike")
car = Car()
car.start()
car.drive()
bike = Bike()
bike.start()
bike.ride()
5. Hybrid Inheritance
A combination of two or more types of inheritance.
Diagram:
/\
B C
\/
Example (Hybrid Inheritance)
class A:
def a(self):
print("A")
class B(A):
def b(self):
print("B")
class C(A):
def c(self):
print("C")
class D(B, C):
def d(self):
print("D")
obj = D()
obj.a()
obj.b()
obj.c()
obj.d()
Task: Employee Management System
Create classes for Employee, Manager, and Developer using inheritance.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def show(self):
print(f"Name: {self.name}, Salary: {self.salary}")
class Manager(Employee):
def __init__(self, name, salary, team_size):
super().__init__(name, salary)
self.team_size = team_size
def show(self):
super().show()
print(f"Team Size: {self.team_size}")
class Developer(Employee):
def __init__(self, name, salary, language):
super().__init__(name, salary)
self.language = language
def show(self):
super().show()
print(f"Language: {self.language}")
m = Manager("Ravi", 90000, 10)
d = Developer("Anu", 70000, "Python")
m.show()
print("-----")
d.show()
super() in Inheritance
Used to call methods from the parent class.
class A:
def show(self):
print("A class method")
class B(A):
def show(self):
super().show()
print("B class method")
B().show()
Encapsulation and information hiding
Encapsulation is an Object-Oriented Programming (OOP) concept where the data
(attributes) and the code (methods) operating on the data are bundled together into a single
unit, i.e., a class.
It helps to protect data from unauthorized access and allows controlled access through
methods.
Information Hiding means restricting access to the internal state of an object and exposing
only what is necessary.
It supports the concept of abstraction and security, making sure implementation details are
hidden from the user.
Benefits of Encapsulation & Information Hiding
Protects object integrity by preventing unauthorized access/modification
Increases security
Improves maintainability and readability
Enables modularity and abstraction
Example 1: Encapsulation using Class
class Employee:
def __init__(self, name, salary):
self.name = name # public
self.__salary = salary # private
def show(self):
print(f"Name: {self.name}, Salary: {self.__salary}")
def increase_salary(self, amount):
if amount > 0:
self.__salary += amount
emp = Employee("Raj", 50000)
emp.show()
emp.increase_salary(5000)
emp.show()
# Direct access to __salary (won’t work)
# print(emp.__salary) AttributeError
Example 2: Protected Members
class Person:
def __init__(self, name, age):
self._name = name # Protected
self._age = age
def show(self):
print(f"{self._name} is {self._age} years old")
class Student(Person):
def display(self):
print(f"Student name is {self._name}")
s = Student("Ravi", 20)
s.show()
s.display()
Protected members are accessible in subclasses but should not be accessed from outside
directly (by convention). But in python there is no concept of protected member.
Example 3: Getter and Setter Methods
Encapsulation is best practiced using getter and setter methods for private attributes.
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def get_balance(self):
return self.__balance
def set_balance(self, amount):
if amount >= 0:
self.__balance = amount
else:
print("Invalid amount!")
account = BankAccount(10000)
print("Balance:", account.get_balance())
account.set_balance(12000)
print("Updated Balance:", account.get_balance())
Search Algorithms
A search algorithm is a method to find a specific item (like a number or a record) in a
collection such as a list, array, or tree.
It returns either the location (index) of the item or indicates that the item is not present.
Types of Search Algorithms
Type Description
Linear Search Check each element one by one
Binary Search Repeatedly divide sorted array in half
1. Linear Search
Scan elements one by one.
Works on unsorted or sorted data.
Simple, but slow for large datasets.
Example:
def linear_search(arr, target):
for i in range(len(arr)):
if arr[i] == target:
return i # Return index
return -1 # Not found
data = [5, 3, 8, 6, 7]
print(linear_search(data, 6))
2. Binary Search
Works on sorted data only.
Repeatedly divides the search interval in half.
Much faster than linear for large data.
Example:
def binary_search(arr, target):
left = 0
right = len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
data = [1, 3, 5, 7, 9, 11]
print(binary_search(data, 7))
Sorting Algorithms
A sorting algorithm is a method used to rearrange elements in a list or array into a specific
order — usually in ascending or descending order.
In Python, sorting algorithms help organize data (like numbers, strings, or objects) for
easier searching, analysis, or presentation.
Why is Sorting Important?
Improves search speed (binary search needs sorted data)
Organizes output (like ranking or reports)
Prepares data for algorithms like searching, merging, etc.
Types of Sorting Orders
1. Ascending Order → [1, 3, 5, 8]
2. Descending Order → [8, 5, 3, 1]
Example: Built-in Sorting in Python
data = [5, 2, 9, 1, 7]
# Ascending sort
data.sort()
print(data) # Output: [1, 2, 5, 7, 9]
# Descending sort
data.sort(reverse=True)
print(data) # Output: [9, 7, 5, 2, 1]
Or use sorted() (returns a new list):
data = [3, 1, 4, 2]
sorted_data = sorted(data)
print(sorted_data) # [1, 2, 3, 4]
Common Sorting Algorithms
1. Bubble Sort
Logic:
Compare each pair of adjacent items.
Swap if they are in the wrong order.
Repeat until the list is sorted.
Example:
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1): # Last i elements are already in place
if arr[j] > arr[j + 1]: # Swap if the element found is greater
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
print(bubble_sort([64, 25, 12, 22, 11]))
2. Selection Sort
Logic:
Find the minimum element in the unsorted part.
Swap it with the first unsorted element.
Example:
def selection_sort(arr):
for i in range(len(arr)):
min_idx = i
for j in range(i + 1, len(arr)):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
print(selection_sort([64, 25, 12, 22, 11]))
3. Insertion Sort
Logic:
Build the sorted array one element at a time.
Insert each item into its correct position.
Example:
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j=i-1
while j >= 0 and key < arr[j]: # Move elements that are greater
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
print(insertion_sort([64, 25, 12, 22, 11]))
Hashtable
A hashtable (or hash map) is a data structure that stores key-value pairs. It uses a hash
function to compute an index (called a hash code) into an array of buckets, from which the
desired value can be found.
In Python, dictionaries (dict) are the built-in implementation of hashtables.
Features of Hashtable (Python dict)
Key-based access to values.
Keys must be unique.
Values can be any type.
Example of a Hashtable (using dict)
# Creating a hashtable (dictionary)
student = {
"name": "Ravi",
"age": 21,
"course": "Python"
}
# Accessing values
print(student["name"]) # Output: Ravi
# Adding a new key-value pair
student["email"] = "
[email protected]"
# Updating a value
student["age"] = 22
# Removing a key
del student["course"]
# Checking if key exists
if "email" in student:
print("Email is", student["email"])
# Iterating through keys and values
for key, value in student.items():
print(key, ":", value)
Common Dictionary (Hashtable) Operations
Operation Syntax Description
Create d = {} Empty hashtable
Add/Update d[key] = value Add or update key-value pair
Access d[key] Get value for a key
Remove del d[key] Delete a key
Keys/Values/Items d.keys(), d.values() Return all keys, values, or key-value pairs
Iterate for k, v in d.items(): Loop through key-value pairs
Example: Word Counter
def word_counter(text):
words = text.lower().split()
count = {}
for word in words:
if word in count:
count[word] += 1
else:
count[word] = 1
return count
text = "Python is fast and Python is easy"
print(word_counter(text))