Exceptions: Handling the Unexpected
Exceptions are Python's way of signaling that something went wrong. Rather than returning error codes, Python raises exception objects that propagate up the call stack until caught or crash the program.
# Basic try/except
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero!")
# Catch multiple exception types
try:
num = int(input("Enter a number: "))
result = 10 / num
except ValueError:
print("That's not a valid number")
except ZeroDivisionError:
print("Cannot divide by zero")
# Catch multiple in one clause
try:
data = int("abc")
except (ValueError, TypeError) as e:
print(f"Conversion error: {e}")
# else โ runs if NO exception occurred
try:
result = 10 / 2
except ZeroDivisionError:
print("Error!")
else:
print(f"Success: {result}") # Runs if no exception
# finally โ ALWAYS runs, exception or not
try:
f = open("file.txt")
data = f.read()
except FileNotFoundError:
data = ""
finally:
# Cleanup โ though 'with' is better for files
print("Done")Exception Hierarchy
# Common built-in exceptions
# BaseException
# Exception
# ValueError โ wrong value type
# TypeError โ wrong type
# KeyError โ dict key not found
# IndexError โ list index out of range
# AttributeError โ object doesn't have attribute
# NameError โ variable not defined
# FileNotFoundError โ file doesn't exist (IOError subclass)
# ImportError โ module can't be imported
# ZeroDivisionError โ division by zero
# RuntimeError โ catch-all runtime errors
# Catching Exception catches most exceptions (but not SystemExit, KeyboardInterrupt)
try:
risky_operation()
except Exception as e:
print(f"Something went wrong: {type(e).__name__}: {e}")Raising Exceptions
def divide(a, b):
if b == 0:
raise ZeroDivisionError("Second argument cannot be zero")
return a / b
def set_age(age):
if not isinstance(age, int):
raise TypeError(f"Age must be int, got {type(age).__name__}")
if age < 0 or age > 150:
raise ValueError(f"Age {age} is not realistic")
return age
# Re-raise exception after logging
try:
risky_operation()
except Exception as e:
log_error(e)
raise # Re-raises the same exception, preserving traceback
# Raise from another exception (exception chaining)
try:
raw = open("config.json").read()
except FileNotFoundError as e:
raise RuntimeError("Configuration not found") from eCustom Exceptions
class AppError(Exception):
"""Base exception for this application."""
pass
class ValidationError(AppError):
"""Raised when input data is invalid."""
def __init__(self, field, message):
self.field = field
self.message = message
super().__init__(f"Validation error on '{field}': {message}")
class DatabaseError(AppError):
"""Raised when database operations fail."""
pass
# Using custom exceptions
def validate_email(email):
if "@" not in email:
raise ValidationError("email", "Must contain @")
if "." not in email.split("@")[1]:
raise ValidationError("email", "Invalid domain")
return email
try:
validate_email("not-an-email")
except ValidationError as e:
print(f"Field: {e.field}, Error: {e.message}")
except AppError as e:
print(f"Application error: {e}")Exception Best Practices
# GOOD: catch specific exceptions
try:
value = int(user_input)
except ValueError:
value = 0
# BAD: bare except catches everything including KeyboardInterrupt
try:
something()
except: # Never do this
pass
# BAD: too broad
try:
something()
except Exception:
pass # Silently swallowing errors is rarely correct
# GOOD: EAFP style (Easier to Ask Forgiveness than Permission)
try:
result = my_dict[key]
except KeyError:
result = default_value
# vs LBYL style (Look Before You Leap) โ also fine
if key in my_dict:
result = my_dict[key]
else:
result = default_valueKey Takeaways
- Catch specific exceptions: never bare
except: - else block runs on success: good for code that depends on try succeeding
- finally always runs: use for cleanup (but prefer context managers)
- Custom exceptions: inherit from Exception, add context via attributes
- EAFP is Pythonic: try it and handle failure rather than checking first
Practice Exercises
- Write a safe
safe_divide(a, b)that returns None on error and logs which error occurred. - Create a custom exception hierarchy for a web app:
AppError,AuthError,NotFoundError,ValidationError. - Write a function that reads a JSON config file. Raise a clear
ConfigErrorif the file is missing, malformed, or missing required keys. - Write a
retry_on_failure(func, max_attempts, exceptions)function that retries a function on specified exceptions.