Session 1: Functions in Python
Function definition & calling
Function arguments (positional, keyword, default, arbitrary *args, **kwargs)
Return values & scope (local vs global)
Lambda functions
Exercise: Write functions for basic math operations, factorial using recursion.
Session 2: Advanced Functions
Higher-order functions
map(), filter(), reduce()
Function closures
Partial functions (functools.partial)
Exercise: Implement a closure that returns a function to multiply numbers by a fixed number.
Session 3: Object-Oriented Programming (OOP) - Basics
Classes & objects
__init__() and instance attributes
Class attributes & instance attributes
self keyword
Exercise: Create a Car class with attributes like brand, model, and year.
Session 4: OOP - Inheritance & Polymorphism
Single & multiple inheritance
Method overriding
super() function
Polymorphism in Python
Exercise: Create a Vehicle class and extend it with Car and Bike classes.
Session 5: OOP - Encapsulation & Magic Methods
Public, protected, and private attributes
Property decorators (@property)
Dunder (magic) methods (__str__, __repr__, __len__, etc.)
Exercise: Implement a class that represents a BankAccount with deposit and withdraw methods.
Session 6: Iterators & Generators
Iterators (__iter__() and __next__())
Generators & yield keyword
Generator expressions
Exercise: Write a generator function to generate Fibonacci numbers.
Session 7: Decorators in Python
Function decorators (@staticmethod, @classmethod, @property)
Custom decorators
Chaining decorators
Exercise: Create a decorator that logs function execution time.
Session 8: Modules & Packages
Creating & importing modules
sys.path and module search path
Creating packages (__init__.py)
pip and installing third-party packages
Exercise: Create a package with math utilities (add.py, subtract.py).
Session 9: Exception Handling
try, except, finally
Catching multiple exceptions
Custom exceptions (raise keyword)
Exercise: Implement exception handling in a division function.
Session 10: File Handling
Reading & writing files (open(), read(), write(), with open)
Working with CSV & JSON
Handling file exceptions
Exercise: Write a program to read a CSV file and convert it to JSON.
1. Functions in Python
Example 1: Email Validation Function
Method 1: Using Regular Expression
import re
def is_valid_email(email):
pattern = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
return bool(re.match(pattern, email))
print(is_valid_email("[email protected]")) # True
print(is_valid_email("invalid-email")) # False
Method 2: Using String Methods
def is_valid_email(email):
if "@" not in email or "." not in email:
return False # Must contain '@' and '.'
at_index = email.index("@")
dot_index = email.rfind(".") # Last occurrence of '.'
# Conditions for a valid email
if at_index < 1: # At least one character before '@'
return False
if dot_index < at_index + 2: # At least one character between '@' and '.'
return False
if dot_index >= len(email) - 1: # At least one character after '.'
return False
return True
# Test cases
print(is_valid_email("[email protected]")) # True
print(is_valid_email("userexample.com")) # False
print(is_valid_email("@example.com")) # False
print(is_valid_email("[email protected]")) # False
Method 3: Using split()
def is_valid_email(email):
parts = email.split("@")
if len(parts) != 2:
return False # Must have exactly one '@'
local, domain = parts
if not local or not domain:
return False # Both local and domain parts must be non-empty
if "." not in domain:
return False # Domain must contain '.'
domain_parts = domain.split(".")
if any(not part for part in domain_parts):
return False # No empty sections in the domain
return True
# Test cases
print(is_valid_email("[email protected]")) # True
print(is_valid_email("user@domain")) # False
print(is_valid_email("user@domain.")) # False
print(is_valid_email("userdomain.com")) # False
Example 2: Currency Converter
def convert_currency(amount, rate):
return round(amount * rate, 2)
usd_to_inr = 83.2
print(convert_currency(100, usd_to_inr)) # Converts 100 USD to INR
2. Advanced Functions
Example 1: Using map() for Temperature Conversion
celsius = [0, 10, 20, 30, 40]
fahrenheit = list(map(lambda c: (c * 9/5) + 32, celsius))
print(fahrenheit) # [32.0, 50.0, 68.0, 86.0, 104.0]
Example 2: Closure for Discount Calculation
def discount(rate):
def apply_discount(price):
return price - (price * rate / 100)
return apply_discount
discount_10 = discount(10)
print(discount_10(500)) # 450.0 (10% discount)
3. OOP - Basics
Example 1: Employee Class
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def show(self):
print(f"Employee Name: {self.name}, Salary: {self.salary}")
emp = Employee("John Doe", 50000)
emp.show()
Example 2: Student Management System
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
def display(self):
print(f"Student: {self.name}, Grade: {self.grade}")
s1 = Student("Alice", "A")
s1.display()
4. OOP - Inheritance & Polymorphism
Example 1: Inheritance in Banking System
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def deposit(self, amount):
self.balance += amount
print(f"Deposited: {amount}, Balance: {self.balance}")
class SavingsAccount(BankAccount):
def add_interest(self):
interest = self.balance * 0.05
self.balance += interest
print(f"Interest added: {interest}, New Balance: {self.balance}")
acc = SavingsAccount(1000)
acc.deposit(500)
acc.add_interest()
Python supports five types of inheritance:
1. Single Inheritance
2. Multiple Inheritance
3. Multilevel Inheritance
4. Hierarchical Inheritance
5. Hybrid Inheritance
1. Single Inheritance
One base (parent) class and one derived (child) class.
Example: Employee and Manager
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def show_details(self):
print(f"Employee: {self.name}, Salary: {self.salary}")
class Manager(Employee): # Inheriting from Employee
def __init__(self, name, salary, department):
super().__init__(name, salary)
self.department = department
def show_manager(self):
print(f"Manager of {self.department} Department")
m = Manager("Alice", 70000, "HR")
m.show_details() # Inherited method
m.show_manager() # Manager-specific method
2. Multiple Inheritance
A child class inherits from multiple parent classes.
Example: Hybrid Car (Combining Electric & Petrol Engine)
class ElectricEngine:
def battery_capacity(self):
return "Battery: 75 kWh"
class PetrolEngine:
def fuel_capacity(self):
return "Fuel: 40 Liters"
class HybridCar(ElectricEngine, PetrolEngine): # Multiple Inheritance
def show_details(self):
print(self.battery_capacity())
print(self.fuel_capacity())
car = HybridCar()
car.show_details()
3. Multilevel Inheritance
A chain of inheritance where a class inherits from another derived class.
Example: Grandparent → Parent → Child
class Grandparent:
def family_name(self):
return "Smith"
class Parent(Grandparent):
def parent_job(self):
return "Engineer"
class Child(Parent): # Multilevel Inheritance
def child_hobby(self):
return "Painting"
c = Child()
print(c.family_name()) # Inherited from Grandparent
print(c.parent_job()) # Inherited from Parent
print(c.child_hobby()) # Defined in Child class
4. Hierarchical Inheritance
Multiple child classes inherit from the same parent class.
Example: Animal Classification
class Animal:
def sound(self):
return "Some sound"
class Dog(Animal):
def sound(self):
return "Barks"
class Cat(Animal):
def sound(self):
return "Meows"
d = Dog()
c = Cat()
print(d.sound()) # Barks
print(c.sound()) # Meows
5. Hybrid Inheritance
A combination of two or more types of inheritance.
Example: School System (Hybrid of Multiple & Hierarchical Inheritance)
class Person:
def __init__(self, name):
self.name = name
class Student(Person):
def student_id(self, id):
return f"Student ID: {id}"
class Teacher(Person):
def teacher_subject(self, subject):
return f"Teaches: {subject}"
class TeachingAssistant(Student, Teacher): # Hybrid Inheritance
def role(self):
return "Teaching Assistant"
ta = TeachingAssistant("John")
print(ta.name) # Inherited from Person
print(ta.student_id(101)) # Inherited from Student
print(ta.teacher_subject("Math")) # Inherited from Teacher
print(ta.role()) # Own method
Example 2: Polymorphism in Shape Classes
class Shape:
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radius
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side * self.side
shapes = [Circle(5), Square(4)]
for shape in shapes:
print(f"Area: {shape.area()}")
5. OOP - Encapsulation & Magic Methods
Example 1: Encapsulation in ATM System
class ATM:
def __init__(self, balance):
self.__balance = balance # Private attribute
def withdraw(self, amount):
if amount <= self.__balance:
self.__balance -= amount
print(f"Withdrawn: {amount}, Remaining: {self.__balance}")
else:
print("Insufficient funds")
atm = ATM(1000)
atm.withdraw(500)
Example 2: Using __str__ Method for Better Representation
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self):
return f"Book: {self.title} by {self.author}"
book = Book("1984", "George Orwell")
print(book)
6.OOP - Abstraction
Abstraction is one of the core principles of Object-Oriented Programming (OOP) that focuses on
hiding implementation details and exposing only the necessary functionality. In Python,
abstraction is achieved using abstract classes and methods with the ABC (Abstract Base Class)
module.
Example 1: Banking System (ATM)
Imagine an ATM machine, where the user interacts with a simple interface but doesn't need to
know the internal working (how it processes transactions, connects to the bank, etc.).
from abc import ABC, abstractmethod
# Abstract class
class ATM(ABC):
@abstractmethod
def deposit(self, amount):
pass
@abstractmethod
def withdraw(self, amount):
pass
# Concrete class
class BankATM(ATM):
def __init__(self, balance):
self.balance = balance
def deposit(self, amount):
self.balance += amount
print(f"Deposited: ${amount}. New Balance: ${self.balance}")
def withdraw(self, amount):
if amount > self.balance:
print("Insufficient balance!")
else:
self.balance -= amount
print(f"Withdrawn: ${amount}. Remaining Balance: ${self.balance}")
# Using the class
my_account = BankATM(500)
my_account.deposit(200)
my_account.withdraw(100)
my_account.withdraw(700) # Should show "Insufficient balance!"
Why Abstraction?
The user only interacts with deposit() and withdraw(), not the internal logic.
The abstract class ensures every ATM must implement deposit() and withdraw(),
enforcing a structure.
Example 2: Vehicle (Car & Bike)
Consider a vehicle system, where different types of vehicles share common behavior but
implement them differently.
from abc import ABC, abstractmethod
# Abstract class
class Vehicle(ABC):
@abstractmethod
def start(self):
pass
@abstractmethod
def stop(self):
pass
# Concrete class - Car
class Car(Vehicle):
def start(self):
print("Car engine started with key ignition.")
def stop(self):
print("Car stopped using brakes.")
# Concrete class - Bike
class Bike(Vehicle):
def start(self):
print("Bike started with self-start button.")
def stop(self):
print("Bike stopped using disc brakes.")
# Using the classes
my_car = Car()
my_car.start()
my_car.stop()
my_bike = Bike()
my_bike.start()
my_bike.stop()
Why Abstraction?
The Vehicle class defines a template (start() and stop()).
Every vehicle (Car, Bike) implements these methods in its own way.
Hides unnecessary details—users just need to know how to start/stop without worrying
about the internals.
Key Takeaways
1. Abstract classes define a blueprint without implementing complete functionality.
2. Concrete classes (subclasses) must provide implementations for the abstract methods.
3. Real-world applications include ATM machines, vehicles, payment gateways, and more.
4. Encapsulation & abstraction work together to create secure and scalable applications.
7. Iterators & Generators
Example 1: Custom Iterator for Fibonacci Series
class Fibonacci:
def __init__(self, limit):
self.limit = limit
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
if self.a > self.limit:
raise StopIteration
value = self.a
self.a, self.b = self.b, self.a + self.b
return value
fib = Fibonacci(50)
for num in fib:
print(num, end=" ")
Example 2: Generator for Even Numbers
def even_numbers(n):
for i in range(0, n, 2):
yield i
for num in even_numbers(10):
print(num, end=" ")
8. Decorators
Example 1: Logging Decorator
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Executing {func.__name__}...")
return func(*args, **kwargs)
return wrapper
@log_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
Example 2: Access Control Decorator
def requires_auth(func):
def wrapper(user):
if user != "admin":
print("Access Denied!")
else:
return func(user)
return wrapper
@requires_auth
def dashboard(user):
print(f"Welcome, {user}!")
dashboard("guest")
dashboard("admin")
9. Modules & Packages
Example 1: Using a Custom Math Module
(math_utils.py)
def add(a, b):
return a + b
def subtract(a, b):
return a - b
(main.py)
import math_utils
print(math_utils.add(5, 3))
print(math_utils.subtract(10, 4))
Example 2: Installing & Using an External Package (requests)
Objective
Use the requests package to send an HTTP request and retrieve data from an API.
Step 1: Install the requests Package
If you haven't installed requests, use the following command:
pip install requests
Step 2: Fetch Data from GitHub API
import requests
# Sending a GET request to GitHub API
response = requests.get("https://api.github.com")
# Printing the response status code
print("Status Code:", response.status_code)
# Displaying the JSON response
if response.status_code == 200:
print("Response JSON:", response.json()) # Prints API response data
else:
print("Failed to fetch data")
Explanation
1. requests.get(url) → Sends a GET request to the provided URL.
2. response.status_code → Returns the HTTP status code (e.g., 200 for success).
3. response.json() → Converts the response data into a Python dictionary (if JSON format).
Expected Output
Status Code: 200
Response JSON: {'current_user_url': 'https://api.github.com/user', ...}
This demonstrates how to use external packages for API communication in Python.
10. Exception Handling
Example 1: Handling Division by Zero
try:
num = int(input("Enter number: "))
print(10 / num)
except ZeroDivisionError:
print("Cannot divide by zero!")
except ValueError:
print("Invalid input! Enter a number.")
Example 2: Custom Exception for Age Validation
class AgeError(Exception):
pass
def validate_age(age):
if age < 18:
raise AgeError("Age must be 18 or above")
print("Age is valid")
try:
validate_age(15)
except AgeError as e:
print(e)
Example 3: Handling Division by Zero in a Billing System
Scenario: A billing system calculates the average bill per customer. If there are
zero customers, it should handle the error.
def calculate_average_bill(total_amount, num_customers):
try:
average = total_amount / num_customers
return f"Average bill per customer: ${average:.2f}"
except ZeroDivisionError:
return "Error: Number of customers cannot be zero."
# Test cases
print(calculate_average_bill(1000, 5)) # Normal case
print(calculate_average_bill(1000, 0)) # Division by zero case
Explanation:
The try block performs division.
If num_customers is zero, a ZeroDivisionError occurs.
The except block handles this and returns an appropriate message.
These examples ensure the application runs smoothly even in unexpected situations.
Creating and Implementing a Custom Exception in Python
In Python, you can create custom exceptions by defining a new class that inherits from the built-
in Exception class.
Scenario: Bank Withdrawal with Insufficient Balance
Let's create a custom exception called InsufficientBalanceError that is raised when a withdrawal
exceeds the account balance.
# Define a custom exception
class InsufficientBalanceError(Exception):
def __init__(self, message="Insufficient balance in your account."):
self.message = message
super().__init__(self.message)
# Bank Account class
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
try:
if amount > self.balance:
raise InsufficientBalanceError(f"Withdrawal failed! Available balance: $
{self.balance}")
self.balance -= amount
return f"Withdrawal successful! New balance: ${self.balance}"
except InsufficientBalanceError as e:
return str(e)
# Example Usage
account = BankAccount(500) # Account with $500 balance
print(account.withdraw(200)) # Successful withdrawal
print(account.withdraw(400)) # Raises InsufficientBalanceError
Explanation:
1. Creating a Custom Exception (InsufficientBalanceError):
o It inherits from Exception.
o The constructor takes a custom error message.
2. Implementation in a Bank Account System:
o The withdraw method checks if the withdrawal amount exceeds the balance.
o If so, it raises InsufficientBalanceError.
o Otherwise, it deducts the amount and updates the balance.
3. Testing the Exception:
o The first withdrawal succeeds.
o The second withdrawal raises the custom exception and displays an error
message.
This ensures that the banking system prevents overdrawing and provides clear feedback to users.
11. File Handling
Example 1: Writing & Reading from a File
with open("data.txt", "w") as f:
f.write("Hello, World!")
with open("data.txt", "r") as f:
print(f.read())
Example 2: Working with CSV Files
import csv
data = [["Name", "Age"], ["Alice", 25], ["Bob", 30]]
with open("people.csv", "w", newline="") as file:
writer = csv.writer(file)
writer.writerows(data)
with open("people.csv", "r") as file:
reader = csv.reader(file)
for row in reader:
print(row)
Handling File Not Found Error in a Logging System
Scenario: A script attempts to read a log file, but the file might not exist .
try:
with open("server_logs.txt", "r") as file:
logs = file.read()
print(logs)
except FileNotFoundError:
print("Error: Log file not found. Please check the file path.")
Explanation:
The try block attempts to open and read the file.
If the file does not exist, a FileNotFoundError is raised.
The except block catches the error and prints a user-friendly message instead of crashing.
Opening a File
file = open("example.txt", "r") # "r" = read mode
Common modes:
"r" – Read (default)
"w" – Write (creates a new file if not exists)
"a" – Append (adds content to an existing file)
"x" – Create (fails if file exists)
"rb" / "wb" – Read/Write in binary mode
Reading a File
with open("example.txt", "r") as file:
content = file.read() # Reads entire file
print(content)
Other reading methods:
.readline() – Reads one line at a time
.readlines() – Reads all lines into a list
Writing to a File
with open("example.txt", "w") as file:
file.write("Hello, this is a test file.\n")
file.write("File handling in Python is easy!")
"w" mode overwrites the file if it exists.
Appending to a File
with open("example.txt", "a") as file:
file.write("\nThis line is appended.")
"a" mode adds content without deleting existing data.
Checking If a File Exists (Before Opening)
import os
if os.path.exists("example.txt"):
print("File exists!")
else:
print("File not found!")
Handling File Exceptions
try:
with open("nonexistent.txt", "r") as file:
content = file.read()
except FileNotFoundError:
print("Error: The file does not exist.")
except Exception as e:
print(f"An error occurred: {e}")
Working with Binary Files (Images, PDFs)
# Reading a binary file
with open("image.jpg", "rb") as file:
data = file.read()
# Writing to a binary file
with open("copy.jpg", "wb") as file:
file.write(data)
Python makes file handling simple with open(), and with open() ensures proper closing of files.
Exception handling ensures safe operations.
Scenario: Employee Attendance System
Objective:
A company maintains an attendance log in a file (attendance.txt). The system should:
1. Check if the file exists before reading.
2. Read existing attendance records from the file.
3. Allow employees to mark attendance (append new records).
4. Handle exceptions if the file is missing or inaccessible.
Implementation
import os
# File name
file_name = "attendance.txt"
# Step 1: Check if the file exists
if not os.path.exists(file_name):
print("Attendance file not found. Creating a new one.")
with open(file_name, "w") as file:
file.write("EmployeeID, Date, Status\n") # Header
# Step 2: Read existing attendance records
print("\n--- Attendance Records ---")
try:
with open(file_name, "r") as file:
records = file.readlines()
for record in records:
print(record.strip()) # Display records line by line
except Exception as e:
print(f"Error reading file: {e}")
# Step 3: Mark attendance (Append to file)
employee_id = input("\nEnter Employee ID: ")
status = input("Enter Status (Present/Absent): ")
try:
with open(file_name, "a") as file:
from datetime import datetime
date_today = datetime.today().strftime('%Y-%m-%d')
file.write(f"{employee_id}, {date_today}, {status}\n")
print("Attendance marked successfully!")
except Exception as e:
print(f"Error writing to file: {e}")
Scenario Workflow
1. Checks if attendance.txt exists:
o If not, it creates a new file with a header.
2. Reads and displays previous attendance records.
3. Prompts the user to enter an Employee ID and attendance status.
4. Appends new attendance data to the file.
5. Handles errors (e.g., missing file, write permission issues).
Example Output
Attendance file not found. Creating a new one.
--- Attendance Records ---
EmployeeID, Date, Status
Enter Employee ID: 101
Enter Status (Present/Absent): Present
Attendance marked successfully!
This approach ensures data persistence, error handling, and easy record management.