Python Exceptions
Master error handling and create robust Python applications
🛡️ Exception Handling
Exceptions are Python's way of handling errors that occur during program execution. Instead of crashing, your program can catch these errors and handle them gracefully, making your applications more robust and user-friendly.
# Basic exception handling
try:
number = int(input("Enter a number: "))
result = 10 / number
print(f"Result: {result}")
except ValueError:
print("Please enter a valid number!")
except ZeroDivisionError:
print("Cannot divide by zero!")
Exception Categories
Arithmetic Errors
Math operation problems
ZeroDivisionError
OverflowError
FloatingPointError
Type Errors
Wrong data type usage
TypeError
ValueError
AttributeError
Lookup Errors
Item not found issues
IndexError
KeyError
NameError
System Errors
OS and file problems
FileNotFoundError
PermissionError
OSError
🔧 Basic Exception Handling
The foundation of error handling in Python
🔹 Try-Except Block
# Basic try-except
try:
age = int(input("Enter your age: "))
print(f"You are {age} years old")
except ValueError:
print("Please enter a valid number")
# Handle multiple exceptions
try:
numbers = [1, 2, 3]
index = int(input("Enter index: "))
print(f"Number at index {index}: {numbers[index]}")
except ValueError:
print("Index must be a number")
except IndexError:
print("Index out of range")
# Catch multiple exceptions together
try:
data = {"name": "Alice", "age": 25}
key = input("Enter key: ")
print(data[key])
except (KeyError, TypeError) as e:
print(f"Error accessing data: {e}")
🔹 Else and Finally
# else - runs if no exception occurs
try:
number = int(input("Enter a number: "))
result = 100 / number
except ValueError:
print("Invalid number")
except ZeroDivisionError:
print("Cannot divide by zero")
else:
print(f"Result: {result}")
print("Calculation successful!")
# finally - always runs
def read_file(filename):
file = None
try:
file = open(filename, 'r')
content = file.read()
return content
except FileNotFoundError:
print(f"File {filename} not found")
return None
finally:
if file:
file.close()
print("File closed")
content = read_file("data.txt")
🔹 Exception Information
# Get exception details
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error type: {type(e).__name__}")
print(f"Error message: {e}")
print(f"Error args: {e.args}")
# Generic exception handler
try:
# Some risky operation
data = [1, 2, 3]
print(data[10])
except Exception as e:
print(f"An error occurred: {e}")
print(f"Error type: {type(e).__name__}")
# Import traceback for detailed info
import traceback
try:
x = 1 / 0
except Exception:
print("Full traceback:")
traceback.print_exc()
📋 Common Exception Types
The most frequently encountered exceptions
🔹 Value and Type Errors
# ValueError - wrong value for correct type
try:
number = int("hello") # Can't convert to int
except ValueError as e:
print(f"ValueError: {e}")
try:
import math
result = math.sqrt(-1) # Negative square root
except ValueError as e:
print(f"Math error: {e}")
# TypeError - wrong type
try:
result = "hello" + 5 # Can't add string and int
except TypeError as e:
print(f"TypeError: {e}")
try:
numbers = [1, 2, 3]
numbers.append() # Missing required argument
except TypeError as e:
print(f"Function error: {e}")
🔹 Index and Key Errors
# IndexError - list index out of range
try:
fruits = ["apple", "banana", "cherry"]
print(fruits[5]) # Index 5 doesn't exist
except IndexError as e:
print(f"IndexError: {e}")
# KeyError - dictionary key doesn't exist
try:
person = {"name": "Alice", "age": 25}
print(person["height"]) # Key doesn't exist
except KeyError as e:
print(f"KeyError: {e}")
# Safe dictionary access
person = {"name": "Alice", "age": 25}
height = person.get("height", "Unknown")
print(f"Height: {height}") # Returns "Unknown"
🔹 Attribute and Name Errors
# AttributeError - object has no attribute
try:
text = "hello"
text.append("world") # Strings don't have append
except AttributeError as e:
print(f"AttributeError: {e}")
# NameError - variable not defined
try:
print(undefined_variable) # Variable doesn't exist
except NameError as e:
print(f"NameError: {e}")
# Check if attribute exists
text = "hello"
if hasattr(text, 'upper'):
print(text.upper())
else:
print("No upper method")
🔹 File and System Errors
# FileNotFoundError
try:
with open("nonexistent.txt", "r") as file:
content = file.read()
except FileNotFoundError as e:
print(f"File error: {e}")
# PermissionError
try:
with open("/root/secret.txt", "w") as file:
file.write("data")
except PermissionError as e:
print(f"Permission error: {e}")
# Safe file operations
import os
def safe_read_file(filename):
if os.path.exists(filename):
try:
with open(filename, "r") as file:
return file.read()
except PermissionError:
return "Permission denied"
else:
return "File not found"
content = safe_read_file("data.txt")
print(content)
🚀 Raising Exceptions
Create and throw your own exceptions
🔹 Raise Built-in Exceptions
# Raise ValueError
def calculate_square_root(number):
if number < 0:
raise ValueError("Cannot calculate square root of negative number")
return number ** 0.5
try:
result = calculate_square_root(-4)
except ValueError as e:
print(f"Error: {e}")
# Raise TypeError
def add_numbers(a, b):
if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
raise TypeError("Both arguments must be numbers")
return a + b
try:
result = add_numbers("5", 3)
except TypeError as e:
print(f"Error: {e}")
# Re-raise exception
def process_data(data):
try:
return int(data)
except ValueError:
print("Logging error...")
raise # Re-raise the same exception
🔹 Custom Exceptions
# Create custom exception
class CustomError(Exception):
"""Custom exception for specific errors"""
pass
class AgeError(Exception):
"""Exception for invalid age values"""
def __init__(self, age, message="Invalid age"):
self.age = age
self.message = message
super().__init__(self.message)
# Use custom exceptions
def validate_age(age):
if age < 0:
raise AgeError(age, "Age cannot be negative")
if age > 150:
raise AgeError(age, "Age seems unrealistic")
return True
try:
validate_age(-5)
except AgeError as e:
print(f"Age validation error: {e}")
print(f"Invalid age was: {e.age}")
# Exception hierarchy
class ValidationError(Exception):
"""Base validation exception"""
pass
class EmailError(ValidationError):
"""Email validation error"""
pass
class PasswordError(ValidationError):
"""Password validation error"""
pass
def validate_email(email):
if "@" not in email:
raise EmailError("Email must contain @")
try:
validate_email("invalid-email")
except ValidationError as e: # Catches all validation errors
print(f"Validation failed: {e}")
🔍 Exception Best Practices
Write better exception handling code
🔹 Specific Exception Handling
# Bad: Too broad
try:
data = process_user_input()
result = calculate(data)
save_result(result)
except Exception: # Catches everything!
print("Something went wrong")
# Good: Specific exceptions
try:
data = process_user_input()
result = calculate(data)
save_result(result)
except ValueError as e:
print(f"Invalid input: {e}")
except ZeroDivisionError as e:
print(f"Math error: {e}")
except FileNotFoundError as e:
print(f"File error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
# Better: Handle at appropriate level
def safe_divide(a, b):
"""Safely divide two numbers"""
try:
return a / b
except ZeroDivisionError:
return None
def process_numbers(numbers):
"""Process list of number pairs"""
results = []
for a, b in numbers:
result = safe_divide(a, b)
if result is not None:
results.append(result)
else:
print(f"Cannot divide {a} by {b}")
return results
🔹 Context Managers
# File handling with context manager
def read_config(filename):
"""Safely read configuration file"""
try:
with open(filename, 'r') as file:
return file.read()
except FileNotFoundError:
print(f"Config file {filename} not found")
return None
except PermissionError:
print(f"No permission to read {filename}")
return None
# Custom context manager
class DatabaseConnection:
def __init__(self, db_name):
self.db_name = db_name
self.connection = None
def __enter__(self):
print(f"Connecting to {self.db_name}")
# Simulate connection
self.connection = f"Connected to {self.db_name}"
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Closing connection to {self.db_name}")
if exc_type:
print(f"Exception occurred: {exc_val}")
return False # Don't suppress exceptions
# Use custom context manager
try:
with DatabaseConnection("mydb") as conn:
print(f"Using {conn}")
# Simulate error
raise ValueError("Database error")
except ValueError as e:
print(f"Handled error: {e}")
🔹 Logging Exceptions
# Proper exception logging
import logging
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def process_data(data):
"""Process data with proper logging"""
try:
# Simulate processing
if not data:
raise ValueError("Empty data provided")
result = len(data) * 2
logging.info(f"Successfully processed data: {result}")
return result
except ValueError as e:
logging.error(f"Data validation error: {e}")
raise
except Exception as e:
logging.exception("Unexpected error in process_data")
raise
# Test with logging
try:
result = process_data("")
except ValueError:
print("Handled validation error")
try:
result = process_data("hello")
print(f"Result: {result}")
except Exception:
print("Handled unexpected error")
📚 Exception Hierarchy
Understanding Python's exception structure
🏗️ Exception Hierarchy:
-
BaseException- Root of all exceptions -
Exception- Base for most exceptions -
ArithmeticError- Math errors -
ZeroDivisionError -
LookupError- Lookup failures -
IndexError,KeyError -
ValueError,TypeError -
SystemExit- Program exit -
KeyboardInterrupt- Ctrl+C
# Exception hierarchy in action
def demonstrate_hierarchy():
"""Show how exception hierarchy works"""
# Catch specific exception
try:
numbers = [1, 2, 3]
print(numbers[10])
except IndexError:
print("Caught IndexError specifically")
# Catch parent exception
try:
numbers = [1, 2, 3]
print(numbers[10])
except LookupError: # Parent of IndexError
print("Caught LookupError (parent)")
# Catch grandparent exception
try:
numbers = [1, 2, 3]
print(numbers[10])
except Exception: # Grandparent
print("Caught Exception (grandparent)")
demonstrate_hierarchy()
# Multiple exception levels
try:
# This could raise various exceptions
data = {"numbers": [1, 2, 3]}
key = input("Enter key: ")
index = int(input("Enter index: "))
result = data[key][index]
print(f"Result: {result}")
except KeyError:
print("Dictionary key not found")
except IndexError:
print("List index out of range")
except ValueError:
print("Invalid number format")
except LookupError: # Catches KeyError and IndexError
print("General lookup error")
except Exception as e:
print(f"Other error: {e}")
# Check exception relationships
print(f"Is IndexError a LookupError? {issubclass(IndexError, LookupError)}")
print(f"Is ValueError an Exception? {issubclass(ValueError, Exception)}")
print(f"Is Exception a BaseException? {issubclass(Exception, BaseException)}")