Exception Handling — Dealing with Errors Gracefully
Even the most carefully written programs can run into unexpected problems — a missing file, a network failure, or invalid user input. **Exception handling** is Python’s way to respond to these issues gracefully, keeping your program from crashing and allowing it to recover or fail politely.
Chapter 2: Control Structures and Functions
Sub-chapter: Exception Handling — Dealing with Errors Gracefully
Even the most carefully written programs can run into unexpected problems — a missing file, a network failure, or invalid user input. Exception handling is Python’s way to respond to these issues gracefully, keeping your program from crashing and allowing it to recover or fail politely.
🧠 What Are Exceptions?
An exception is an event that disrupts the normal flow of execution.
In Python, when something goes wrong (like dividing by zero), the interpreter raises an exception — effectively saying “I don’t know how to handle this.”
There are three main types of problems in code:
| Type | Description | Example |
|---|---|---|
| Syntax Error | Mistake in the structure of the code, caught before execution. | if x = 5: |
| Runtime Error (Exception) | Error during program execution. | 10 / 0 |
| Logical Error | Code runs but gives incorrect result. | Using + instead of * |
⚙️ Python Exception Hierarchy (Simplified)
BaseException
├── Exception
│ ├── ArithmeticError
│ │ ├── ZeroDivisionError
│ │ └── OverflowError
│ ├── ValueError
│ ├── TypeError
│ ├── FileNotFoundError
│ ├── IndexError
│ ├── KeyError
│ └── RuntimeError
└── SystemExit, KeyboardInterrupt (special cases)
💡 Most of the time, you’ll be working with the
Exceptionbranch of this hierarchy.
🧩 Handling Exceptions with try and except
Python provides the try–except structure to handle errors safely.
Syntax:
try:
# Code that might raise an exception
except ExceptionType:
# Code to handle that exception
Example — Handling division by zero:
try:
result = 10 / 0
except ZeroDivisionError:
print("Error: Division by zero is not allowed.")
Output:
Error: Division by zero is not allowed.
⚙️ Handling Multiple Exceptions
You can handle different types of errors with separate except blocks.
try:
number = int(input("Enter a number: "))
result = 10 / number
except ValueError:
print("Error: Please enter a valid integer.")
except ZeroDivisionError:
print("Error: Division by zero is not allowed.")
Or, handle multiple exceptions in one block:
except (ValueError, ZeroDivisionError):
print("Invalid input or division by zero.")
🔄 Using else and finally
else— runs only if no exception occurred.finally— always runs, whether or not an exception was raised (useful for cleanup).
Example:
try:
file = open("data.txt", "r")
content = file.read()
except FileNotFoundError:
print("File not found.")
else:
print("File read successfully!")
finally:
print("Closing file...")
file.close()
Output:
File not found.
Closing file...
✅ Use
finallyfor cleanup tasks like closing files, releasing network connections, or freeing resources.
🚨 Raising Exceptions Manually
You can raise exceptions intentionally using the raise keyword — for example, to enforce rules or validate inputs.
def validate_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
print("Age is valid!")
Example use:
try:
validate_age(-5)
except ValueError as e:
print(f"Validation failed: {e}")
Output:
Validation failed: Age cannot be negative
⚙️ Custom Exception Classes
You can define your own exception types by subclassing Exception.
This helps create more meaningful and domain-specific error handling.
class InvalidEmailError(Exception):
"""Raised when an email address is not valid."""
pass
def validate_email(email):
if "@" not in email:
raise InvalidEmailError("Email must contain '@' symbol.")
print("Valid email!")
try:
validate_email("example.com")
except InvalidEmailError as e:
print("Error:", e)
Output:
Error: Email must contain '@' symbol.
🔗 Exception Chaining (raise ... from)
Sometimes, you want to raise a new exception but preserve the original context.
try:
value = int("abc")
except ValueError as e:
raise RuntimeError("Failed to convert string to int") from e
This makes debugging easier by preserving the original traceback.
🧰 Common Built‑in Exceptions
| Exception | Description |
|---|---|
ZeroDivisionError | Raised when dividing by zero |
ValueError | Raised when an operation receives the wrong value type |
TypeError | Raised when an operation is applied to the wrong data type |
FileNotFoundError | Raised when a file is not found |
IndexError | Raised when accessing an invalid list index |
KeyError | Raised when a dictionary key is missing |
ImportError | Raised when an import fails |
RuntimeError | Generic error for unexpected runtime issues |
AttributeError | Raised when accessing a missing attribute |
OSError | Raised for system-level errors (files, I/O, etc.) |
🌐 Real-World Examples
Example 1 — Handling Invalid User Input
try:
temp = float(input("Enter temperature: "))
except ValueError:
print("Please enter a valid number!")
Example 2 — File Operations
try:
with open("config.txt") as f:
data = f.read()
except FileNotFoundError:
print("Configuration file missing. Creating a new one...")
open("config.txt", "w").write("default=true")
Example 3 — Network Operation
import requests
try:
response = requests.get("https://example.com")
response.raise_for_status()
except requests.exceptions.RequestException as e:
print("Network error:", e)
✅ Best Practices for Exception Handling
- Be specific: Catch specific exceptions instead of
Exceptionwhen possible. - Avoid silent failures: Don’t leave empty
exceptblocks. - Use
finallyfor cleanup (files, sockets, etc.). - Raise meaningful errors with clear messages.
- Log exceptions in production systems for troubleshooting.
- Don’t use exceptions for normal logic flow.
🧾 Key Takeaways
- Exceptions let your program handle errors gracefully.
- Use
try,except,else, andfinallyto control behavior. - Raise your own exceptions when inputs or conditions are invalid.
- Learn common exception types and catch only what you expect.
- Use custom exceptions and chaining for complex applications.
With proper exception handling, your programs become resilient, user-friendly, and reliable — capable of recovering from errors rather than collapsing under them.