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.