Python Exception Handling: The Complete Beginner's Guide
This chapter teaches robust error handling with \`try/except/else/finally\`, \`raise\`, custom exceptions, chaining, and practical patterns so programs fail gracefully.
Chapter 15 of 20 · Intermediate · 40 min · Python Programming Course
Every real program eventually encounters invalid input, missing files, network failures, or unexpected states. Exception handling lets your code respond gracefully instead of crashing.
What You Will Learn in This Chapter
By the end of this tutorial you will be able to:
- Understand what exceptions are and why they happen
- Use
try,except,else, andfinallycorrectly - Catch specific exceptions and avoid broad catches
- Access exception objects using
as - Raise exceptions intentionally with
raise - Define custom exception classes
- Chain exceptions with
raise ... from - Apply practical exception-handling patterns
- Decide when to handle and when to propagate
- Avoid common exception-handling mistakes
Estimated time: 40 minutes reading + 20 minutes practice
What Is an Exception?
An exception is an object raised when execution cannot continue normally. Python searches for a matching handler up the call stack.
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero")
Exception Hierarchy
BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception
├── ValueError
├── TypeError
├── NameError
├── AttributeError
├── IndexError
├── KeyError
├── FileNotFoundError
├── PermissionError
└── OSError
Catch specific exceptions whenever possible.
try/except/else/finally
try:
value = int(input("Enter a number: "))
result = 100 / value
except ValueError:
print("Not a valid integer")
except ZeroDivisionError:
print("Division by zero is not allowed")
else:
print(f"Result: {result}")
finally:
print("Done")
elseruns only on success.finallyalways runs (great for cleanup).
Catching Multiple Exceptions
def to_int(value, default=None):
try:
return int(value)
except (ValueError, TypeError):
return default
Accessing Exception Details
try:
with open("config.json") as f:
data = f.read()
except FileNotFoundError as e:
print(f"Missing file: {e.filename}")
Raising Exceptions
def set_age(age):
if not isinstance(age, int):
raise TypeError("Age must be an integer")
if age < 0:
raise ValueError("Age cannot be negative")
Re-raising and Chaining
def load_config(path):
try:
with open(path) as f:
return f.read()
except FileNotFoundError as e:
raise RuntimeError("Configuration load failed") from e
Use bare raise inside an except block when you need to log and propagate unchanged.
Custom Exceptions
class RegistrationError(Exception):
pass
class StudentNotFoundError(RegistrationError):
def __init__(self, student_id):
self.student_id = student_id
super().__init__(f"Student not found: {student_id}")
Custom exceptions make domain errors explicit and easier to handle.
Practical Patterns
Input validation loop
def get_valid_integer(prompt):
while True:
try:
return int(input(prompt))
except ValueError:
print("Please enter a whole number")
Retry logic
import time
def with_retry(fn, attempts=3):
last = None
for i in range(attempts):
try:
return fn()
except Exception as e:
last = e
if i < attempts - 1:
time.sleep(0.5 * (2 ** i))
raise last
A Complete Working Program
Student registration system with custom exceptions:
class RegistrationError(Exception):
pass
class StudentNotFoundError(RegistrationError):
def __init__(self, student_id):
self.student_id = student_id
super().__init__(f"Student not found: {student_id}")
class AlreadyEnrolledError(RegistrationError):
pass
class RegistrationSystem:
def __init__(self):
self.students = {}
def add_student(self, sid, name):
if sid in self.students:
raise RegistrationError(f"Student ID {sid} already exists")
self.students[sid] = {"name": name, "courses": []}
return f"{name} added"
def enroll(self, sid, course):
if sid not in self.students:
raise StudentNotFoundError(sid)
student = self.students[sid]
if course in student["courses"]:
raise AlreadyEnrolledError(f"{student['name']} already in {course}")
student["courses"].append(course)
return f"{student['name']} enrolled in {course}"
5 Exception Handling Mistakes
- Using bare
except: - Swallowing errors silently with
pass - Putting too much code inside one try block
- Using exceptions for normal control flow
- Losing context instead of using
raise ... from
Practice Exercises
- Date parser with robust validation
- Safe CSV processor with row-level error reporting
- Custom bank exception hierarchy
- Retry decorator implementation
- Safe execution wrapper with structured results
-> See all Python practice exercises with solutions
What Comes Next - Day 16: Advanced Data Structures
Next you will focus on efficient structures:
collections.dequecollections.Countercollections.defaultdict- named tuples
- heaps with
heapq - choosing the right structure for performance
-> Continue to Day 16: Advanced Data Structures
Frequently Asked Questions
What is exception handling in Python?
Exception handling is the mechanism for managing runtime errors without crashing a program.
Difference between try, except, else, and finally?
try holds risky code, except handles errors, else runs on success, and finally always runs.
Difference between raise and raise ... from?
raise signals an error; raise ... from preserves causal context between exceptions.
Should I use bare except?
Almost never. Catch specific exceptions to avoid masking bugs and control-flow signals.
What are custom exceptions?
Application-specific exception classes representing meaningful domain errors.
What is exception chaining?
Linking a new exception to the original cause using raise NewError(...) from e.
How do I decide what to catch?
Catch only exceptions you can meaningfully recover from.
Difference between errors and exceptions?
In practice, both are exception objects; “error” is usually a conceptual label.
Chapter navigation
- Previous: Day 14: File Handling
- Next: Day 16: Advanced Data Structures
- Python Quiz: Take the Python quiz
- All Python exercises: Explore Python exercises
Frequently asked questions: Exception handling
What is exception handling in Python?
Exception handling is Python’s mechanism to catch runtime errors and respond gracefully instead of crashing.
What is the difference between try, except, else, and finally?
try contains risky code, except handles matching errors, else runs only if no error occurred, and finally always runs.
What is the difference between raise and raise ... from?
raise signals an exception; raise ... from links a new exception to an original cause to preserve debugging context.
When should I use a bare except clause?
Almost never. Bare except catches too much, including interrupts and system-exit signals.
What are custom exceptions in Python?
Custom exceptions are application-specific classes derived from Exception to model domain-specific error conditions.
What is exception chaining in Python?
Exception chaining connects related failures using raise NewError(...) from original_error.
How do I know which exceptions to catch?
Catch the most specific exceptions you can recover from and let others propagate.
What is the difference between errors and exceptions in Python?
Practically all runtime errors are represented as exception objects; the distinction is mostly conceptual.
Related Page
Day 14: File Handling
Learn about day 14: file handling
Learn MoreDay 16: Advanced Data Structures
Continue with day 16: advanced data structures
Learn MorePython exercises hub
165+ programs with solutions
Learn MorePython quiz
Multiple-choice checks with explanations
Learn MoreBasic Python programs
Exception handling practice programs
Learn More