Python Error Handling & Exceptions Cheat Sheet
Quick Reference Card
| Operation | Syntax | Example |
|---|---|---|
| Try/except | try: ... except: |
try: x = 1/0 except: pass |
| Specific exception | except TypeError: |
except ValueError as e: |
| Multiple exceptions | except (Type1, Type2): |
except (ValueError, KeyError): |
| Catch all | except Exception as e: |
except Exception as e: |
| Else clause | try: ... else: |
Execute if no exception |
| Finally clause | try: ... finally: |
Always executes |
| Raise exception | raise Exception(msg) |
raise ValueError('Invalid') |
| Custom exception | class MyError(Exception): |
Define custom error |
| Context manager | with ... as: |
with open('f') as f: |
| Assert | assert condition |
assert x > 0 |
Table of Contents
- Exception Basics
- Try/Except/Else/Finally
- Common Built-in Exceptions
- Raising Exceptions
- Custom Exceptions
- Exception Chaining
- Context Managers
- Best Practices
Exception Basics
What Are Exceptions?
# Exceptions are errors detected during execution
# They interrupt normal program flow
# Without exception handling (program crashes)
result = 10 / 0 # ZeroDivisionError: division by zero
# With exception handling (program continues)
try:
result = 10 / 0
except ZeroDivisionError:
result = None
print('Cannot divide by zero')
Basic Exception Handling
# Simple try/except
try:
number = int('abc') # ValueError
except ValueError:
print('Invalid number format')
# Capture exception details
try:
number = int('abc')
except ValueError as e:
print(f'Error: {e}') # Error: invalid literal for int()
Try/Except/Else/Finally
Try/Except Block
# Basic form
try:
# Code that might raise an exception
result = 10 / 2
except ZeroDivisionError:
# Handle the exception
result = None
print('Division by zero')
Multiple Except Clauses
# Handle different exceptions differently
try:
value = int(input('Enter a number: '))
result = 10 / value
except ValueError:
print('Invalid input - not a number')
except ZeroDivisionError:
print('Cannot divide by zero')
except Exception as e:
print(f'Unexpected error: {e}')
Catching Multiple Exceptions Together
# Same handling for multiple exceptions
try:
data = {'key': 'value'}
result = data['missing_key']
number = int('abc')
except (KeyError, ValueError) as e:
print(f'Error occurred: {e}')
# Or separately
try:
process_data()
except (IOError, OSError) as e:
print(f'File operation failed: {e}')
Else Clause
# Executes if NO exception is raised
try:
result = 10 / 2
except ZeroDivisionError:
print('Division by zero')
else:
print(f'Result: {result}') # Executes if no error
# Use else for code that should run only if try succeeds
# Practical example
def read_file(filename):
try:
f = open(filename, 'r')
except FileNotFoundError:
print(f'File {filename} not found')
return None
else:
# Only read if file was opened successfully
content = f.read()
f.close()
return content
Finally Clause
# ALWAYS executes, even if exception occurs
try:
file = open('data.txt', 'r')
content = file.read()
except FileNotFoundError:
print('File not found')
finally:
# Always executes - cleanup code
try:
file.close()
except:
pass
print('Cleanup complete')
Complete Try/Except/Else/Finally Example
def divide_numbers(a, b):
"""Complete exception handling example"""
result = None
try:
# Try to perform the operation
result = a / b
except ZeroDivisionError:
# Handle specific error
print('Error: Cannot divide by zero')
except TypeError:
# Handle another specific error
print('Error: Invalid types for division')
else:
# Executes if no exception occurred
print(f'Division successful: {result}')
finally:
# Always executes (cleanup)
print('Division operation completed')
return result
# Usage
divide_numbers(10, 2) # Success
divide_numbers(10, 0) # ZeroDivisionError
divide_numbers(10, 'a') # TypeError
Common Built-in Exceptions
Commonly Used Exceptions
| Exception | When It Occurs | Example |
|---|---|---|
Exception |
Base class for most exceptions | Generic catch-all |
ValueError |
Invalid value for operation | int('abc') |
TypeError |
Wrong type for operation | '2' + 2 |
KeyError |
Key not found in dictionary | dict['missing'] |
IndexError |
Index out of range | list[999] |
FileNotFoundError |
File doesn’t exist | open('missing.txt') |
ZeroDivisionError |
Division by zero | 10 / 0 |
AttributeError |
Attribute doesn’t exist | obj.missing_attr |
ImportError |
Module import fails | import missing_module |
RuntimeError |
Generic runtime error | Various situations |
Exception Examples
# ValueError - Invalid value
try:
number = int('not a number')
except ValueError as e:
print(f'ValueError: {e}')
# TypeError - Wrong type
try:
result = '2' + 2 # Can't add string and int
except TypeError as e:
print(f'TypeError: {e}')
# KeyError - Missing dictionary key
try:
data = {'name': 'Alice'}
age = data['age'] # Key doesn't exist
except KeyError as e:
print(f'KeyError: {e}')
# IndexError - Invalid list index
try:
items = [1, 2, 3]
value = items[10] # Index out of range
except IndexError as e:
print(f'IndexError: {e}')
# FileNotFoundError - File doesn't exist
try:
with open('missing_file.txt', 'r') as f:
content = f.read()
except FileNotFoundError as e:
print(f'FileNotFoundError: {e}')
# AttributeError - Missing attribute
try:
value = None
value.some_method() # None has no method some_method
except AttributeError as e:
print(f'AttributeError: {e}')
Exception Hierarchy
# Exception hierarchy (simplified)
# BaseException
# ├── Exception
# │ ├── ValueError
# │ ├── TypeError
# │ ├── KeyError
# │ ├── IndexError
# │ ├── FileNotFoundError
# │ └── ... (many more)
# ├── KeyboardInterrupt
# └── SystemExit
# Catching parent exception catches all children
try:
value = int('abc') # Raises ValueError
except Exception as e: # Catches ValueError (and most others)
print(f'Caught: {e}')
# Be specific when possible
try:
value = int('abc')
except ValueError: # More specific - better
print('Invalid integer')
except Exception: # Generic fallback
print('Some other error')
Raising Exceptions
Raise Built-in Exceptions
# Raise an exception manually
def divide(a, b):
if b == 0:
raise ZeroDivisionError('Cannot divide by zero')
return a / b
# Raise ValueError for invalid input
def set_age(age):
if age < 0:
raise ValueError('Age cannot be negative')
if age > 150:
raise ValueError('Age seems unrealistic')
return age
# Usage
try:
set_age(-5)
except ValueError as e:
print(f'Error: {e}')
Raise Without Arguments
# Re-raise the current exception
def process_data(data):
try:
result = int(data)
except ValueError:
print('Logging error...')
raise # Re-raise the same exception
# Usage
try:
process_data('invalid')
except ValueError as e:
print(f'Caught re-raised exception: {e}')
Raise Generic Exception
# Raise generic Exception with custom message
def validate_password(password):
if len(password) < 8:
raise Exception('Password must be at least 8 characters')
if not any(c.isdigit() for c in password):
raise Exception('Password must contain at least one digit')
# Usage
try:
validate_password('short')
except Exception as e:
print(f'Validation failed: {e}')
Custom Exceptions
Creating Custom Exceptions
# Basic custom exception
class InvalidAgeError(Exception):
"""Custom exception for invalid age values"""
pass
# Usage
def set_age(age):
if age < 0 or age > 150:
raise InvalidAgeError(f'Invalid age: {age}')
return age
try:
set_age(-5)
except InvalidAgeError as e:
print(f'Error: {e}')
Custom Exception with Attributes
# Exception with additional data
class InsufficientFundsError(Exception):
"""Exception for insufficient account balance"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
self.shortage = amount - balance
super().__init__(f'Insufficient funds: need ${amount}, have ${balance}')
# Usage
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(balance, amount)
return balance - amount
try:
withdraw(100, 150)
except InsufficientFundsError as e:
print(f'Error: {e}')
print(f'Short by: ${e.shortage}')
Exception Hierarchy for Your Application
# Create exception hierarchy
class AppError(Exception):
"""Base exception for application"""
pass
class DatabaseError(AppError):
"""Database-related errors"""
pass
class ValidationError(AppError):
"""Validation errors"""
pass
class AuthenticationError(AppError):
"""Authentication errors"""
pass
# Usage - can catch all app errors or specific ones
def process_user(user_data):
if not user_data.get('email'):
raise ValidationError('Email is required')
# Database operation
if not database_available():
raise DatabaseError('Database connection failed')
try:
process_user({})
except ValidationError as e:
print(f'Validation error: {e}')
except DatabaseError as e:
print(f'Database error: {e}')
except AppError as e:
print(f'Application error: {e}')
Exception Chaining
From Clause (Explicit Chaining)
# Chain exceptions to preserve context
def load_config():
try:
with open('config.json', 'r') as f:
import json
return json.load(f)
except FileNotFoundError as e:
raise RuntimeError('Configuration file not found') from e
except json.JSONDecodeError as e:
raise RuntimeError('Invalid configuration format') from e
# Usage
try:
config = load_config()
except RuntimeError as e:
print(f'Error: {e}')
print(f'Caused by: {e.__cause__}') # Original exception
Implicit Chaining
# Exceptions during exception handling are automatically chained
def process_data():
try:
result = 1 / 0
except ZeroDivisionError:
# This exception is implicitly chained to ZeroDivisionError
undefined_variable # NameError
try:
process_data()
except NameError as e:
print(f'Error: {e}')
print(f'Context: {e.__context__}') # Previous exception
Suppressing Chaining
# Suppress exception chaining with 'from None'
def get_value(data, key):
try:
return data[key]
except KeyError:
# Don't show KeyError in traceback
raise ValueError(f'Required key "{key}" not found') from None
try:
get_value({'name': 'Alice'}, 'age')
except ValueError as e:
print(f'Error: {e}') # Only shows ValueError, not KeyError
Context Managers
What Are Context Managers?
# Context managers ensure cleanup happens
# Even if exceptions occur
# Without context manager (risky)
file = open('data.txt', 'r')
try:
content = file.read()
finally:
file.close() # Must remember to close
# With context manager (safe)
with open('data.txt', 'r') as file:
content = file.read()
# File automatically closed, even if exception occurs
Common Context Managers
# File operations
with open('file.txt', 'r') as f:
content = f.read()
# Database connections
import sqlite3
with sqlite3.connect('database.db') as conn:
cursor = conn.cursor()
cursor.execute('SELECT * FROM users')
# Locks (threading)
from threading import Lock
lock = Lock()
with lock:
# Critical section
shared_resource.modify()
Creating Custom Context Managers (Class)
# Using __enter__ and __exit__ methods
class DatabaseConnection:
"""Custom context manager for database"""
def __init__(self, db_name):
self.db_name = db_name
self.connection = None
def __enter__(self):
"""Called when entering 'with' block"""
print(f'Connecting to {self.db_name}')
self.connection = f'Connection to {self.db_name}'
return self.connection
def __exit__(self, exc_type, exc_val, exc_tb):
"""Called when exiting 'with' block"""
print(f'Closing connection to {self.db_name}')
if exc_type is not None:
print(f'Exception occurred: {exc_val}')
# Return False to propagate exception, True to suppress
return False
# Usage
with DatabaseConnection('mydb') as conn:
print(f'Using {conn}')
# Connection automatically closed after block
Creating Context Managers (Decorator)
from contextlib import contextmanager
@contextmanager
def temporary_file(filename):
"""Context manager using generator"""
print(f'Creating {filename}')
f = open(filename, 'w')
try:
yield f # This is what 'as' captures
finally:
print(f'Cleaning up {filename}')
f.close()
import os
os.remove(filename)
# Usage
with temporary_file('temp.txt') as f:
f.write('Temporary data')
# File automatically deleted after block
Practical Context Manager Examples
from contextlib import contextmanager
import time
@contextmanager
def timer(name):
"""Measure execution time"""
start = time.time()
print(f'{name} started')
try:
yield
finally:
elapsed = time.time() - start
print(f'{name} took {elapsed:.2f} seconds')
# Usage
with timer('Data processing'):
# Your code here
time.sleep(1)
process_large_dataset()
# Temporary directory
import tempfile
import shutil
@contextmanager
def temp_directory():
"""Create and cleanup temporary directory"""
temp_dir = tempfile.mkdtemp()
try:
yield temp_dir
finally:
shutil.rmtree(temp_dir)
# Usage
with temp_directory() as temp_dir:
# Use temporary directory
create_files(temp_dir)
# Directory automatically deleted
Best Practices
Be Specific with Exceptions
# Bad: Catching all exceptions
try:
result = int(user_input)
except: # Too broad!
print('Something went wrong')
# Good: Catch specific exceptions
try:
result = int(user_input)
except ValueError:
print('Invalid integer format')
except Exception as e:
print(f'Unexpected error: {e}')
Don’t Use Exceptions for Flow Control
# Bad: Using exceptions for normal flow
try:
user = users[user_id]
except KeyError:
user = create_new_user(user_id)
# Good: Use conditional logic
user = users.get(user_id)
if user is None:
user = create_new_user(user_id)
Provide Helpful Error Messages
# Bad: Generic error message
if age < 0:
raise ValueError('Invalid value')
# Good: Specific, actionable message
if age < 0:
raise ValueError(f'Age must be non-negative, got {age}')
Clean Up Resources
# Bad: Manual cleanup
f = open('file.txt', 'r')
content = f.read()
f.close() # Might not execute if error occurs
# Good: Context manager
with open('file.txt', 'r') as f:
content = f.read()
# Automatically closed
Log Exceptions Appropriately
import logging
# Configure logging
logging.basicConfig(level=logging.ERROR)
def process_data(data):
try:
result = complex_operation(data)
return result
except ValueError as e:
logging.error(f'Invalid data: {e}', exc_info=True)
raise
except Exception as e:
logging.exception('Unexpected error in process_data')
raise
Don’t Silence Exceptions
# Bad: Silencing exceptions without handling
try:
critical_operation()
except:
pass # Problems hidden!
# Good: Handle or log appropriately
try:
critical_operation()
except Exception as e:
logging.error(f'Critical operation failed: {e}')
# Re-raise or handle appropriately
raise
Use Exception Chaining
# Bad: Losing original exception context
try:
load_config()
except FileNotFoundError:
raise RuntimeError('Config error') # Lost original error
# Good: Preserve exception context
try:
load_config()
except FileNotFoundError as e:
raise RuntimeError('Config error') from e # Preserves context
Validate Input Early
def calculate_discount(price, discount_percent):
"""Calculate discount with input validation"""
# Validate inputs first
if not isinstance(price, (int, float)):
raise TypeError('Price must be a number')
if price < 0:
raise ValueError('Price cannot be negative')
if not 0 <= discount_percent <= 100:
raise ValueError('Discount must be between 0 and 100')
# Now perform calculation
return price * (1 - discount_percent / 100)
Comprehensive Example
import logging
from pathlib import Path
# Configure logging
logging.basicConfig(level=logging.INFO)
class FileProcessingError(Exception):
"""Custom exception for file processing"""
pass
def process_file(filename):
"""Process file with comprehensive error handling"""
file_path = Path(filename)
# Validate input
if not isinstance(filename, (str, Path)):
raise TypeError('Filename must be a string or Path')
# Check file exists
if not file_path.exists():
raise FileNotFoundError(f'File not found: {filename}')
# Check file is readable
if not file_path.is_file():
raise ValueError(f'Not a file: {filename}')
# Process file
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Process content
result = content.upper() # Example processing
logging.info(f'Successfully processed {filename}')
return result
except PermissionError as e:
logging.error(f'Permission denied: {filename}')
raise FileProcessingError(f'Cannot read {filename}') from e
except UnicodeDecodeError as e:
logging.error(f'Encoding error in {filename}')
raise FileProcessingError(f'Invalid encoding in {filename}') from e
except Exception as e:
logging.exception(f'Unexpected error processing {filename}')
raise FileProcessingError(f'Failed to process {filename}') from e
# Usage
try:
result = process_file('data.txt')
print(result)
except FileNotFoundError as e:
print(f'File error: {e}')
except FileProcessingError as e:
print(f'Processing error: {e}')
if e.__cause__:
print(f'Caused by: {e.__cause__}')
except Exception as e:
print(f'Unexpected error: {e}')
See Also
- File Operations Cheat Sheet - File handling with error handling
- Python Basics Cheat Sheet - Python fundamentals
- Testing and Debugging Cheat Sheet - Testing exception handling