Errors happen. Good programs handle them gracefully instead of crashing. Python's exception system lets you catch errors, recover from them, and provide helpful feedback to users.
Without error handling:
age = int(input("Enter age: ")) # User types "twenty"
ValueError: invalid literal for int() with base 10: 'twenty'
Program crashes!
With error handling:
try:
age = int(input("Enter age: "))
except ValueError:
print("Please enter a number!")
age = None
Program continues running
try:
# Code that might fail
number = int("abc")
except ValueError:
# Runs if ValueError occurs
print("Can't convert 'abc' to integer!")try:
numbers = [1, 2, 3]
index = int(input("Enter index: "))
print(numbers[index])
except ValueError:
print("Please enter a valid number!")
except IndexError:
print(f"Index out of range (0-{len(numbers)-1})")try:
# Risky operation
result = risky_function()
except Exception as e:
# Catches ALL exceptions
print(f"Error: {e}")
print(f"Error type: {type(e).__name__}")Warning: Catching Exception catches almost everything. Usually better to catch specific exceptions.
try:
# Code that could fail in multiple ways
value = data[key] / divisor
except (KeyError, ZeroDivisionError, TypeError) as e:
print(f"Operation failed: {e}")try:
number = int("not a number")
except ValueError as e:
print(f"Error message: {e}")
print(f"Error type: {type(e).__name__}")
# Full traceback details
import traceback
print(traceback.format_exc())Runs if NO exception occurred:
try:
number = int(input("Enter a number: "))
result = 100 / number
except ValueError:
print("Invalid input!")
except ZeroDivisionError:
print("Cannot divide by zero!")
else:
# Only runs if try succeeded
print(f"Result: {result}")
print("Calculation successful!")Always runs, whether exception occurred or not:
try:
file = open("data.txt", "r")
data = file.read()
except FileNotFoundError:
print("File not found!")
data = None
finally:
# Always runs - clean up resources
if 'file' in locals():
file.close()
print("Cleanup complete")try:
# Try to do something risky
result = process_data()
except SpecificError:
# Handle specific error
handle_error()
except AnotherError as e:
# Handle another type
print(f"Error: {e}")
else:
# Only if try succeeded
print("Success!")
finally:
# Always runs
cleanup()# ValueError - invalid value for type
int("abc") # ValueError
TypeError - wrong type
"hello" + 5 # TypeError
KeyError - missing dictionary key
d = {"a": 1}
d["b"] # KeyError
IndexError - invalid list index
lst = [1, 2, 3]
lst[10] # IndexError
AttributeError - invalid attribute
"hello".nonexistent # AttributeError
FileNotFoundError - file doesn't exist
open("missing.txt") # FileNotFoundError
ZeroDivisionError - division by zero
10 / 0 # ZeroDivisionError
ImportError - can't import module
import nonexistent_module # ImportErrorThrow your own errors:
def set_age(age):
if age < 0:
raise ValueError("Age cannot be negative!")
if age > 150:
raise ValueError("Age seems unrealistic!")
return age
try:
set_age(-5)
except ValueError as e:
print(f"Invalid age: {e}")def process_file(filename):
try:
with open(filename) as f:
data = f.read()
except FileNotFoundError:
print(f"Logging: {filename} not found")
raise # Re-raise same exception
return data
try:
process_file("missing.txt")
except FileNotFoundError:
print("Handled at higher level")Create your own exception types:
class InvalidEmailError(Exception):
"""Raised when email format is invalid."""
pass
class PasswordTooShortError(Exception):
"""Raised when password is too short."""
def __init__(self, length, minimum=8):
self.length = length
self.minimum = minimum
super().__init__(
f"Password is {length} characters, minimum is {minimum}"
)
def validate_email(email):
if "@" not in email:
raise InvalidEmailError(f"Invalid email: {email}")
def validate_password(password):
if len(password) < 8:
raise PasswordTooShortError(len(password))
try:
validate_email("notanemail")
except InvalidEmailError as e:
print(f"Error: {e}")
try:
validate_password("short")
except PasswordTooShortError as e:
print(f"Error: {e}")
print(f"Need {e.minimum - e.length} more characters")Automatically handle setup and cleanup:
# Without context manager - manual cleanup
file = open("data.txt", "w")
try:
file.write("Hello")
finally:
file.close() # Must remember to close
With context manager - automatic cleanup
with open("data.txt", "w") as file:
file.write("Hello")
File automatically closed, even if error occurs!
Multiple resources
with open("input.txt") as infile, open("output.txt", "w") as outfile:
data = infile.read()
outfile.write(data.upper())class Timer:
def __enter__(self):
"""Called when entering 'with' block."""
import time
self.start = time.time()
print("Timer started")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Called when exiting 'with' block."""
import time
elapsed = time.time() - self.start
print(f"Timer stopped. Elapsed: {elapsed:.2f}s")
# Return False to propagate exceptions
return False
Use it
with Timer():
# Your code here
sum(range(1000000))
Timer started
Timer stopped. Elapsed: 0.03s
Check conditions during development:
def calculate_average(numbers):
assert len(numbers) > 0, "List cannot be empty"
assert all(isinstance(n, (int, float)) for n in numbers), "All items must be numbers"
return sum(numbers) / len(numbers)
Assertions enabled
calculate_average([]) # AssertionError: List cannot be empty
Assertions can be disabled with -O flag
# python -O script.pyUse assertions for:
def get_positive_number(prompt):
"""Keep asking until valid positive number entered."""
while True:
try:
value = float(input(prompt))
if value <= 0:
print("Please enter a positive number!")
continue
return value
except ValueError:
print("Invalid input! Please enter a number.")
age = get_positive_number("Enter your age: ")def process_file(filename):
"""Process file with comprehensive error handling."""
try:
with open(filename, 'r') as f:
data = f.read()
except FileNotFoundError:
print(f"Error: '{filename}' not found")
return None
except PermissionError:
print(f"Error: No permission to read '{filename}'")
return None
except Exception as e:
print(f"Unexpected error reading '{filename}': {e}")
return None
# Process data
try:
result = process_data(data)
except ValueError as e:
print(f"Error processing data: {e}")
return None
return resultimport timedef fetch_data(url, max_retries=3):
"""Fetch data with automatic retry on failure."""
for attempt in range(max_retries):
try:
response = make_request(url)
return response
except ConnectionError as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff
print(f"Connection failed. Retrying in {wait_time}s...")
time.sleep(wait_time)
else:
print(f"Failed after {max_retries} attempts")
raise
except Timeout:
print("Request timed out")
return None
class DatabaseError(Exception):
pass
def execute_transaction(operations):
"""Execute multiple database operations atomically."""
try:
db.begin_transaction()
for operation in operations:
operation.execute()
db.commit()
print("Transaction successful")
except DatabaseError as e:
db.rollback()
print(f"Transaction failed: {e}")
raise
finally:
db.close_connection()class ValidationError(Exception):
"""Base class for validation errors."""
pass
class EmailValidationError(ValidationError):
pass
class AgeValidationError(ValidationError):
pass
class UserValidator:
@staticmethod
def validate_email(email):
if not isinstance(email, str):
raise EmailValidationError("Email must be a string")
if "@" not in email or "." not in email:
raise EmailValidationError(f"Invalid email format: {email}")
@staticmethod
def validate_age(age):
if not isinstance(age, int):
raise AgeValidationError("Age must be an integer")
if age < 0 or age > 150:
raise AgeValidationError(f"Age out of valid range: {age}")
@classmethod
def validate_user(cls, email, age):
"""Validate all user data."""
errors = []
try:
cls.validate_email(email)
except EmailValidationError as e:
errors.append(str(e))
try:
cls.validate_age(age)
except AgeValidationError as e:
errors.append(str(e))
if errors:
raise ValidationError(f"Validation failed: {', '.join(errors)}")
Use it
try:
UserValidator.validate_user("invalid", -5)
except ValidationError as e:
print(f"User validation failed: {e}")Exceptionfinally or withtry-except to handle errors gracefullyelse for code that runs only on successfinally for cleanup that always runswith statements for automatic resource management