CS Pathfinder Logo CS Pathfinder


Unit 4 · Modules and File Handling

Exception Handling

Learn how to handle runtime errors gracefully in Python using try-except blocks, common exception types, and custom exception creation.

Introduction

When a Python program encounters an error during execution, it raises an exception. If the exception is not handled, the program crashes. Exception handling allows you to catch and respond to errors gracefully, preventing unexpected crashes and providing meaningful error messages to users.

Exception handling is one of the most important topics in Python. It appears in virtually every university exam and is essential for writing robust, production-quality code.

University Definition

An exception is an event that occurs during program execution that disrupts the normal flow of instructions. Exception handling is the process of catching and responding to exceptions using the try, except, else, and finally blocks, allowing the program to continue executing or terminate gracefully.

Table of Contents

What are Exceptions?

Exceptions are errors detected during execution. They are different from syntax errors (which are caught before the program runs).

# This is a SYNTAX ERROR (caught before execution)
# if True
#     print("Hello")
# SyntaxError: invalid syntax

# This is an EXCEPTION (caught during execution)
x = 10
y = 0
# result = x / y  # ZeroDivisionError: division by zero

# Another exception example
name = "Hello"
# print(name[10])  # IndexError: string index out of range

Difference Between Errors and Exceptions

Feature Errors Exceptions
When Detected Before execution (syntax errors) During execution
Handleable? No — must fix the code Yes — using try-except
Examples SyntaxError, IndentationError ValueError, TypeError, FileNotFoundError

The try-except Block

# Basic try-except syntax
try:
    # Code that might raise an exception
    x = int(input("Enter a number: "))
    result = 100 / x
    print("Result:", result)
except:
    # Code that runs if an exception occurs
    print("Something went wrong!")

# Output (if user enters 0):
# Enter a number: 0
# Something went wrong!

# Output (if user enters 5):
# Enter a number: 5
# Result: 20.0

Catching Specific Exceptions

# Catching specific exceptions
try:
    num = int(input("Enter a number: "))
    result = 100 / num
    my_list = [1, 2, 3]
    print(my_list[10])
except ValueError:
    print("Invalid input! Please enter a number.")
except ZeroDivisionError:
    print("Cannot divide by zero!")
except IndexError:
    print("Index out of range!")
except Exception as e:
    print(f"Unexpected error: {e}")

# Each except block handles a different type of error

University Exam Tip

Always catch specific exceptions rather than using a bare except:. The bare except catches everything including SystemExit and KeyboardInterrupt, which is usually not desired.

The else and finally Blocks

University Definition

The else block runs only if no exception was raised in the try block. The finally block runs always, whether an exception occurred or not. It is typically used for cleanup operations like closing files or releasing resources.

try:
    f = open("data.txt", "r")
    content = f.read()
except FileNotFoundError:
    print("File not found!")
except PermissionError:
    print("Permission denied!")
else:
    # Runs only if no exception occurred
    print("File read successfully!")
    print(f"Content length: {len(content)} characters")
finally:
    # Runs ALWAYS, regardless of exceptions
    print("Cleanup: Operation complete.")

# Possible outputs:
# If file exists:
#   File read successfully!
#   Content length: 245 characters
#   Cleanup: Operation complete.

# If file doesn't exist:
#   File not found!
#   Cleanup: Operation complete.

Complete Flow

try:
    # Attempt dangerous code
    risky_operation()
except SomeException:
    # Handle specific error
    handle_error()
except AnotherException:
    # Handle another error
    handle_other_error()
else:
    # No error occurred
    success_operation()
finally:
    # Always runs (cleanup)
    cleanup_operation()

Raising Exceptions with raise

You can manually raise exceptions using the raise keyword. This is useful when you want to signal that an error has occurred in your function.

def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative!")
    if age > 150:
        raise ValueError("Age seems unrealistic!")
    print(f"Age set to {age}")

# Using the function
try:
    set_age(25)      # Age set to 25
    set_age(-5)      # Raises ValueError
except ValueError as e:
    print(f"Error: {e}")  # Error: Age cannot be negative!

# Re-raising exceptions
try:
    x = int("abc")
except ValueError:
    print("Logging the error...")
    raise  # Re-raises the same exception

Common Exception Types

Exception Cause Example
ValueError Wrong value type int("abc")
TypeError Wrong data type "hello" + 5
ZeroDivisionError Division by zero 10 / 0
IndexError Index out of range [1,2][5]
KeyError Dictionary key not found {"a":1}["b"]
FileNotFoundError File doesn't exist open("xyz.txt")
AttributeError Invalid attribute/method "hello".foo()
ImportError Module not found import nonexistent
# Demonstrating common exceptions

# ValueError
try:
    x = int("hello")
except ValueError as e:
    print(f"ValueError: {e}")
    # ValueError: invalid literal for int() with base 10: 'hello'

# TypeError
try:
    result = "5" + 3
except TypeError as e:
    print(f"TypeError: {e}")
    # TypeError: can only concatenate str (not "int") to str

# ZeroDivisionError
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"ZeroDivisionError: {e}")
    # ZeroDivisionError: division by zero

# IndexError
try:
    lst = [1, 2, 3]
    print(lst[10])
except IndexError as e:
    print(f"IndexError: {e}")
    # IndexError: list index out of range

# FileNotFoundError
try:
    f = open("nonexistent.txt")
except FileNotFoundError as e:
    print(f"FileNotFoundError: {e}")

Creating Custom Exceptions

You can create your own exception classes by inheriting from the built-in Exception class.

# Custom exception class
class InsufficientFundsError(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(
            f"Cannot withdraw {amount}. Balance is only {balance}."
        )

# Using the custom exception
class BankAccount:
    def __init__(self, balance=0):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(self.balance, amount)
        self.balance -= amount
        return self.balance

# Test
account = BankAccount(1000)

try:
    account.withdraw(500)
    print(f"Balance: {account.balance}")   # Balance: 500
    account.withdraw(600)                  # Raises InsufficientFundsError
except InsufficientFundsError as e:
    print(f"Error: {e}")
    # Error: Cannot withdraw 600. Balance is only 500.

Practical Examples

Example 1: Safe Input Validation

def get_valid_number(prompt):
    """Keep asking until user provides a valid number."""
    while True:
        try:
            return int(input(prompt))
        except ValueError:
            print("Invalid input! Please enter a whole number.")

age = get_valid_number("Enter your age: ")
print(f"Your age is {age}")

Example 2: Safe File Processing

def safe_read_file(filename):
    """Read a file safely with exception handling."""
    try:
        with open(filename, "r") as f:
            return f.read()
    except FileNotFoundError:
        print(f"Error: File '{filename}' not found.")
    except PermissionError:
        print(f"Error: No permission to read '{filename}'.")
    except Exception as e:
        print(f"Unexpected error: {e}")
    return None

content = safe_read_file("data.txt")
if content:
    print(content)

Example 3: Calculator with Full Exception Handling

def calculator():
    try:
        a = float(input("Enter first number: "))
        op = input("Enter operator (+, -, *, /): ")
        b = float(input("Enter second number: "))

        if op == "+":
            result = a + b
        elif op == "-":
            result = a - b
        elif op == "*":
            result = a * b
        elif op == "/":
            if b == 0:
                raise ZeroDivisionError("Cannot divide by zero!")
            result = a / b
        else:
            raise ValueError(f"Unknown operator: {op}")

        print(f"Result: {a} {op} {b} = {result}")

    except ValueError as e:
        print(f"Invalid input: {e}")
    except ZeroDivisionError as e:
        print(f"Math error: {e}")
    except Exception as e:
        print(f"Unexpected error: {e}")
    finally:
        print("Calculator operation complete.")

calculator()

Key Points

try contains code that might raise an exception.

except handles the exception and prevents crashes.

else runs only when no exception occurs.

finally runs always, for cleanup operations.

raise manually triggers an exception.

Always catch specific exceptions, not bare except:.

Custom exceptions inherit from the Exception class.

Use as e to capture the exception object for error messages.

Practice Questions

Q1: What is the difference between an error and an exception?

Answer: Errors (like SyntaxError) are detected before execution and cannot be handled. Exceptions occur during execution and can be caught and handled using try-except.

Q2: What is the role of the finally block?

Answer: The finally block executes always, regardless of whether an exception occurred or not. It is used for cleanup operations like closing files or releasing resources.

Q3: Write a program that handles division by zero and invalid input.

Answer: Use try-except with ZeroDivisionError and ValueError in a calculator function.

Q4: When does the else block in a try-except execute?

Answer: The else block executes only when the try block completes without raising any exception.

Q5: How do you create a custom exception in Python?

Answer: Create a class that inherits from Exception: class MyError(Exception): pass. Use raise MyError("message") to trigger it.

Summary

Exceptions are runtime errors that can be caught and handled.

try-except-else-finally is the complete exception handling structure.

Always catch specific exceptions (ValueError, TypeError, etc.) rather than bare except.

raise allows you to manually trigger exceptions.

Common exceptions: ValueError, TypeError, ZeroDivisionError, IndexError, FileNotFoundError.

Custom exceptions are created by inheriting from the Exception class.

finally runs always and is used for resource cleanup.

Exception handling prevents crashes and enables graceful error recovery.

Python Programming Handwritten Notes

Master Python Programming with Easy Handwritten Notes – Perfect for Interviews, Placements, GATE & Exams.