Exceptions
Raise
Signaling Errors
Your validation function checks if age is negative. You need to signal "this is
wrong" to the caller. raise creates and throws an exception. The caller must
handle it or let it propagate up.
Raise for invalid input
Signal that input doesn't meet requirements.
# Raising exceptions on invalid input
def main():
# Raise on invalid input
def check_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age is unrealistic")
return age
print("Age validation:")
# Valid age
try:
result = check_age(25)
print(f" Age {result}: Valid")
except ValueError as e:
print(f" Error: {e}")
# Invalid age
try:
result = check_age(-5)
print(f" Age {result}: Valid")
except ValueError as e:
print(f" Error: {e}")
# Raise on division by zero
def safe_divide(a, b):
if b == 0:
raise ZeroDivisionError("Cannot divide by zero")
return a / b
print("\nDivision:")
try:
result = safe_divide(10, 2)
print(f" 10 / 2 = {result}")
except ZeroDivisionError as e:
print(f" Error: {e}")
try:
result = safe_divide(10, 0)
print(f" 10 / 0 = {result}")
except ZeroDivisionError as e:
print(f" Error: {e}")
# Validate string input
def validate_username(username):
if not username:
raise ValueError("Username cannot be empty")
if len(username) < 3:
raise ValueError("Username must be at least 3 characters")
if not username.isalnum():
raise ValueError("Username must be alphanumeric")
return username
print("\nUsername validation:")
usernames =
for name in usernames:
try:
valid = validate_username(name)
print(f" '{name}': OK")
except ValueError as e:
print(f" '{name}': {e}")
if __name__ == "__main__":
main()
# Raising exceptions on invalid input
def main():
# Raise on invalid input
def check_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age is unrealistic")
return age
print("Age validation:")
# Valid age
try:
result = check_age(25)
print(f" Age {result}: Valid")
except ValueError as e:
print(f" Error: {e}")
# Invalid age
try:
result = check_age(-5)
print(f" Age {result}: Valid")
except ValueError as e:
print(f" Error: {e}")
# Raise on division by zero
def safe_divide(a, b):
if b == 0:
raise ZeroDivisionError("Cannot divide by zero")
return a / b
print("\nDivision:")
try:
result = safe_divide(10, 2)
print(f" 10 / 2 = {result}")
except ZeroDivisionError as e:
print(f" Error: {e}")
try:
result = safe_divide(10, 0)
print(f" 10 / 0 = {result}")
except ZeroDivisionError as e:
print(f" Error: {e}")
# Validate string input
def validate_username(username):
if not username:
raise ValueError("Username cannot be empty")
if len(username) < 3:
raise ValueError("Username must be at least 3 characters")
if not username.isalnum():
raise ValueError("Username must be alphanumeric")
return username
print("\nUsername validation:")
usernames =
for name in usernames:
try:
valid = validate_username(name)
print(f" '{name}': OK")
except ValueError as e:
print(f" '{name}': {e}")
if __name__ == "__main__":
main()
# Raising exceptions on invalid input
def main():
# Raise on invalid input
def check_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age is unrealistic")
return age
print("Age validation:")
# Valid age
try:
result = check_age(25)
print(f" Age {result}: Valid")
except ValueError as e:
print(f" Error: {e}")
# Invalid age
try:
result = check_age(-5)
print(f" Age {result}: Valid")
except ValueError as e:
print(f" Error: {e}")
# Raise on division by zero
def safe_divide(a, b):
if b == 0:
raise ZeroDivisionError("Cannot divide by zero")
return a / b
print("\nDivision:")
try:
result = safe_divide(10, 2)
print(f" 10 / 2 = {result}")
except ZeroDivisionError as e:
print(f" Error: {e}")
try:
result = safe_divide(10, 0)
print(f" 10 / 0 = {result}")
except ZeroDivisionError as e:
print(f" Error: {e}")
# Validate string input
def validate_username(username):
if not username:
raise ValueError("Username cannot be empty")
if len(username) < 3:
raise ValueError("Username must be at least 3 characters")
if not username.isalnum():
raise ValueError("Username must be alphanumeric")
return username
print("\nUsername validation:")
usernames =
for name in usernames:
try:
valid = validate_username(name)
print(f" '{name}': OK")
except ValueError as e:
print(f" '{name}': {e}")
if __name__ == "__main__":
main()
raise ValueError("message") stops execution and signals error to caller.
Exception propagation
Exceptions bubble up until caught.
# Exception propagation
def main():
# Exception propagates through call stack
def level3():
print(" level3: About to raise")
raise RuntimeError("Error in level3")
def level2():
print(" level2: Calling level3")
level3()
print(" level2: This won't print")
def level1():
print("level1: Calling level2")
level2()
print("level1: This won't print")
print("Propagation demo:\n")
try:
level1()
except RuntimeError as e:
print(f"\nCaught in main: {e}")
# Function that lets exception propagate
def read_config(key):
config = {"host": "localhost", "port": 8080}
# KeyError propagates if key missing
return config[key]
def get_server_url():
host = read_config("host")
port = read_config("port")
return f"http://{host}:{port}"
print("\nConfig reading:")
try:
url = get_server_url()
print(f" URL: {url}")
except KeyError as e:
print(f" Missing config key: {e}")
try:
timeout = read_config("timeout") # Missing key
print(f" Timeout: {timeout}")
except KeyError as e:
print(f" Missing config key: {e}")
# Catch at different levels
def process_data(data):
if not data:
raise ValueError("Data is empty")
return data.upper()
def transform(data):
# Let exception propagate
return process_data(data)
def handle_request(data):
try:
# Catch here
result = transform(data)
return f"Success: {result}"
except ValueError as e:
return f"Error: {e}"
print("\nRequest handling:")
print(f" {handle_request('hello')}")
print(f" {handle_request('')}")
# Chain of operations
def step1(value):
if value < 0:
raise ValueError("Step1: Value must be positive")
return value * 2
def step2(value):
if value > 100:
raise ValueError("Step2: Value too large")
return value + 10
def step3(value):
if value % 2 != 0:
raise ValueError("Step3: Value must be even")
return value / 2
def pipeline(value):
v1 = step1(value)
v2 = step2(v1)
v3 = step3(v2)
return v3
print("\nPipeline processing:")
test_values =
for val in test_values:
try:
result = pipeline(val)
print(f" {val} → {result}")
except ValueError as e:
print(f" {val} → Failed: {e}")
if __name__ == "__main__":
main()
# Exception propagation
def main():
# Exception propagates through call stack
def level3():
print(" level3: About to raise")
raise RuntimeError("Error in level3")
def level2():
print(" level2: Calling level3")
level3()
print(" level2: This won't print")
def level1():
print("level1: Calling level2")
level2()
print("level1: This won't print")
print("Propagation demo:\n")
try:
level1()
except RuntimeError as e:
print(f"\nCaught in main: {e}")
# Function that lets exception propagate
def read_config(key):
config = {"host": "localhost", "port": 8080}
# KeyError propagates if key missing
return config[key]
def get_server_url():
host = read_config("host")
port = read_config("port")
return f"http://{host}:{port}"
print("\nConfig reading:")
try:
url = get_server_url()
print(f" URL: {url}")
except KeyError as e:
print(f" Missing config key: {e}")
try:
timeout = read_config("timeout") # Missing key
print(f" Timeout: {timeout}")
except KeyError as e:
print(f" Missing config key: {e}")
# Catch at different levels
def process_data(data):
if not data:
raise ValueError("Data is empty")
return data.upper()
def transform(data):
# Let exception propagate
return process_data(data)
def handle_request(data):
try:
# Catch here
result = transform(data)
return f"Success: {result}"
except ValueError as e:
return f"Error: {e}"
print("\nRequest handling:")
print(f" {handle_request('hello')}")
print(f" {handle_request('')}")
# Chain of operations
def step1(value):
if value < 0:
raise ValueError("Step1: Value must be positive")
return value * 2
def step2(value):
if value > 100:
raise ValueError("Step2: Value too large")
return value + 10
def step3(value):
if value % 2 != 0:
raise ValueError("Step3: Value must be even")
return value / 2
def pipeline(value):
v1 = step1(value)
v2 = step2(v1)
v3 = step3(v2)
return v3
print("\nPipeline processing:")
test_values =
for val in test_values:
try:
result = pipeline(val)
print(f" {val} → {result}")
except ValueError as e:
print(f" {val} → Failed: {e}")
if __name__ == "__main__":
main()
# Exception propagation
def main():
# Exception propagates through call stack
def level3():
print(" level3: About to raise")
raise RuntimeError("Error in level3")
def level2():
print(" level2: Calling level3")
level3()
print(" level2: This won't print")
def level1():
print("level1: Calling level2")
level2()
print("level1: This won't print")
print("Propagation demo:\n")
try:
level1()
except RuntimeError as e:
print(f"\nCaught in main: {e}")
# Function that lets exception propagate
def read_config(key):
config = {"host": "localhost", "port": 8080}
# KeyError propagates if key missing
return config[key]
def get_server_url():
host = read_config("host")
port = read_config("port")
return f"http://{host}:{port}"
print("\nConfig reading:")
try:
url = get_server_url()
print(f" URL: {url}")
except KeyError as e:
print(f" Missing config key: {e}")
try:
timeout = read_config("timeout") # Missing key
print(f" Timeout: {timeout}")
except KeyError as e:
print(f" Missing config key: {e}")
# Catch at different levels
def process_data(data):
if not data:
raise ValueError("Data is empty")
return data.upper()
def transform(data):
# Let exception propagate
return process_data(data)
def handle_request(data):
try:
# Catch here
result = transform(data)
return f"Success: {result}"
except ValueError as e:
return f"Error: {e}"
print("\nRequest handling:")
print(f" {handle_request('hello')}")
print(f" {handle_request('')}")
# Chain of operations
def step1(value):
if value < 0:
raise ValueError("Step1: Value must be positive")
return value * 2
def step2(value):
if value > 100:
raise ValueError("Step2: Value too large")
return value + 10
def step3(value):
if value % 2 != 0:
raise ValueError("Step3: Value must be even")
return value / 2
def pipeline(value):
v1 = step1(value)
v2 = step2(v1)
v3 = step3(v2)
return v3
print("\nPipeline processing:")
test_values =
for val in test_values:
try:
result = pipeline(val)
print(f" {val} → {result}")
except ValueError as e:
print(f" {val} → Failed: {e}")
if __name__ == "__main__":
main()
Uncaught exception travels up the call stack to the first handler.
Include error details
Provide useful information in the exception message.
# Raising exceptions with messages
def main():
# Detailed error messages
def withdraw(balance, amount):
if amount <= 0:
raise ValueError(f"Withdrawal amount must be positive, got {amount}")
if amount > balance:
raise ValueError(f"Insufficient funds: balance={balance}, requested={amount}")
return balance - amount
print("Bank withdrawals:\n")
balance =
# Successful withdrawal
try:
new_balance = withdraw(balance, 30)
print(f" Withdrew $30: New balance ${new_balance}")
balance = new_balance
except ValueError as e:
print(f" Error: {e}")
# Invalid amount
try:
new_balance = withdraw(balance, -10)
print(f" Withdrew $-10: New balance ${new_balance}")
except ValueError as e:
print(f" Error: {e}")
# Insufficient funds
try:
new_balance = withdraw(balance, 200)
print(f" Withdrew $200: New balance ${new_balance}")
except ValueError as e:
print(f" Error: {e}")
# Type-specific error messages
def calculate_discount(price, discount_percent):
if not isinstance(price, (int, float)):
raise TypeError(f"Price must be numeric, got {type(price).__name__}")
if not isinstance(discount_percent, (int, float)):
raise TypeError(f"Discount must be numeric, got {type(discount_percent).__name__}")
if discount_percent < 0 or discount_percent > 100:
raise ValueError(f"Discount must be 0-100%, got {discount_percent}")
return price * (1 - discount_percent / 100)
print("\nDiscount calculations:")
test_cases = [
(100, 10),
(100, "10"),
("100", 10),
(100, 150)
]
for price, discount in test_cases:
try:
final_price = calculate_discount(price, discount)
print(f" ${price} - {discount}% = ${final_price:.2f}")
except (TypeError, ValueError) as e:
print(f" ${price} - {discount}%: {e}")
# Index validation with context
def get_item(items, index):
if not isinstance(index, int):
raise TypeError(f"Index must be integer, got {type(index).__name__}")
if index < 0:
raise IndexError(f"Index must be non-negative, got {index}")
if index >= len(items):
raise IndexError(f"Index {index} out of range (list size: {len(items)})")
return items[index]
print("\nList access:")
fruits = ["apple", "banana", "cherry"]
indices = [0, 5, -1, "2"]
for idx in indices:
try:
fruit = get_item(fruits, idx)
print(f" fruits[{idx}] = {fruit}")
except (TypeError, IndexError) as e:
print(f" fruits[{idx}]: {e}")
if __name__ == "__main__":
main()
# Raising exceptions with messages
def main():
# Detailed error messages
def withdraw(balance, amount):
if amount <= 0:
raise ValueError(f"Withdrawal amount must be positive, got {amount}")
if amount > balance:
raise ValueError(f"Insufficient funds: balance={balance}, requested={amount}")
return balance - amount
print("Bank withdrawals:\n")
balance =
# Successful withdrawal
try:
new_balance = withdraw(balance, 30)
print(f" Withdrew $30: New balance ${new_balance}")
balance = new_balance
except ValueError as e:
print(f" Error: {e}")
# Invalid amount
try:
new_balance = withdraw(balance, -10)
print(f" Withdrew $-10: New balance ${new_balance}")
except ValueError as e:
print(f" Error: {e}")
# Insufficient funds
try:
new_balance = withdraw(balance, 200)
print(f" Withdrew $200: New balance ${new_balance}")
except ValueError as e:
print(f" Error: {e}")
# Type-specific error messages
def calculate_discount(price, discount_percent):
if not isinstance(price, (int, float)):
raise TypeError(f"Price must be numeric, got {type(price).__name__}")
if not isinstance(discount_percent, (int, float)):
raise TypeError(f"Discount must be numeric, got {type(discount_percent).__name__}")
if discount_percent < 0 or discount_percent > 100:
raise ValueError(f"Discount must be 0-100%, got {discount_percent}")
return price * (1 - discount_percent / 100)
print("\nDiscount calculations:")
test_cases = [
(100, 10),
(100, "10"),
("100", 10),
(100, 150)
]
for price, discount in test_cases:
try:
final_price = calculate_discount(price, discount)
print(f" ${price} - {discount}% = ${final_price:.2f}")
except (TypeError, ValueError) as e:
print(f" ${price} - {discount}%: {e}")
# Index validation with context
def get_item(items, index):
if not isinstance(index, int):
raise TypeError(f"Index must be integer, got {type(index).__name__}")
if index < 0:
raise IndexError(f"Index must be non-negative, got {index}")
if index >= len(items):
raise IndexError(f"Index {index} out of range (list size: {len(items)})")
return items[index]
print("\nList access:")
fruits = ["apple", "banana", "cherry"]
indices = [0, 5, -1, "2"]
for idx in indices:
try:
fruit = get_item(fruits, idx)
print(f" fruits[{idx}] = {fruit}")
except (TypeError, IndexError) as e:
print(f" fruits[{idx}]: {e}")
if __name__ == "__main__":
main()
# Raising exceptions with messages
def main():
# Detailed error messages
def withdraw(balance, amount):
if amount <= 0:
raise ValueError(f"Withdrawal amount must be positive, got {amount}")
if amount > balance:
raise ValueError(f"Insufficient funds: balance={balance}, requested={amount}")
return balance - amount
print("Bank withdrawals:\n")
balance =
# Successful withdrawal
try:
new_balance = withdraw(balance, 30)
print(f" Withdrew $30: New balance ${new_balance}")
balance = new_balance
except ValueError as e:
print(f" Error: {e}")
# Invalid amount
try:
new_balance = withdraw(balance, -10)
print(f" Withdrew $-10: New balance ${new_balance}")
except ValueError as e:
print(f" Error: {e}")
# Insufficient funds
try:
new_balance = withdraw(balance, 200)
print(f" Withdrew $200: New balance ${new_balance}")
except ValueError as e:
print(f" Error: {e}")
# Type-specific error messages
def calculate_discount(price, discount_percent):
if not isinstance(price, (int, float)):
raise TypeError(f"Price must be numeric, got {type(price).__name__}")
if not isinstance(discount_percent, (int, float)):
raise TypeError(f"Discount must be numeric, got {type(discount_percent).__name__}")
if discount_percent < 0 or discount_percent > 100:
raise ValueError(f"Discount must be 0-100%, got {discount_percent}")
return price * (1 - discount_percent / 100)
print("\nDiscount calculations:")
test_cases = [
(100, 10),
(100, "10"),
("100", 10),
(100, 150)
]
for price, discount in test_cases:
try:
final_price = calculate_discount(price, discount)
print(f" ${price} - {discount}% = ${final_price:.2f}")
except (TypeError, ValueError) as e:
print(f" ${price} - {discount}%: {e}")
# Index validation with context
def get_item(items, index):
if not isinstance(index, int):
raise TypeError(f"Index must be integer, got {type(index).__name__}")
if index < 0:
raise IndexError(f"Index must be non-negative, got {index}")
if index >= len(items):
raise IndexError(f"Index {index} out of range (list size: {len(items)})")
return items[index]
print("\nList access:")
fruits = ["apple", "banana", "cherry"]
indices = [0, 5, -1, "2"]
for idx in indices:
try:
fruit = get_item(fruits, idx)
print(f" fruits[{idx}] = {fruit}")
except (TypeError, IndexError) as e:
print(f" fruits[{idx}]: {e}")
if __name__ == "__main__":
main()
Include the problematic value: raise ValueError(f"Invalid age: {age}").
Re-raise current exception
Catch, do something, then let it propagate.
# Re-raising exceptions after logging
def main():
# Log and re-raise
def process_payment(amount):
if amount <= 0:
raise ValueError("Amount must be positive")
print(f" Processing payment: ${amount}")
return {"status": "success", "amount": amount}
def handle_payment(amount):
try:
return process_payment(amount)
except ValueError as e:
print(f" [LOG] Payment failed: {e}")
raise # Re-raise the same exception
print("Payment processing:\n")
try:
result = handle_payment(100)
print(f"Result: {result}")
except ValueError as e:
print(f"Main caught: {e}")
print()
try:
result = handle_payment(-50)
print(f"Result: {result}")
except ValueError as e:
print(f"Main caught: {e}")
# Cleanup and re-raise
def database_operation(query):
connection = "DB_CONNECTION"
print(f" Opened: {connection}")
try:
if "invalid" in query:
raise RuntimeError(f"Bad query: {query}")
print(f" Executed: {query}")
return "RESULT"
except RuntimeError:
print(f" Error occurred, cleaning up...")
print(f" Closed: {connection}")
raise # Re-raise after cleanup
finally:
# This runs regardless
pass
print("\nDatabase operations:")
try:
result = database_operation("SELECT * FROM users")
print(f" Success: {result}\n")
except RuntimeError as e:
print(f" Failed: {e}\n")
try:
result = database_operation("invalid syntax")
print(f" Success: {result}")
except RuntimeError as e:
print(f" Failed: {e}")
# Conditional re-raise
def validate_and_parse(data):
try:
value = int(data)
if value < 0:
raise ValueError("Value must be non-negative")
return value
except ValueError as e:
if "invalid literal" in str(e):
# Handle parse errors locally
print(f" Parse error, using default")
return 0
else:
# Re-raise validation errors
raise
print("\nValidation and parsing:")
test_data =
for data in test_data:
try:
result = validate_and_parse(data)
print(f" '{data}' → {result}")
except ValueError as e:
print(f" '{data}' → Error: {e}")
# Transform and re-raise
def risky_operation():
items = [1, 2, 3]
return items[10] # IndexError
def wrapped_operation():
try:
return risky_operation()
except IndexError as e:
print(f" [LOG] IndexError: {e}")
# Could transform exception type here
raise RuntimeError("Operation failed") from e
print("\nWrapped operation:")
try:
wrapped_operation()
except RuntimeError as e:
print(f" Caught RuntimeError: {e}")
print(f" Caused by: {e.__cause__}")
if __name__ == "__main__":
main()
# Re-raising exceptions after logging
def main():
# Log and re-raise
def process_payment(amount):
if amount <= 0:
raise ValueError("Amount must be positive")
print(f" Processing payment: ${amount}")
return {"status": "success", "amount": amount}
def handle_payment(amount):
try:
return process_payment(amount)
except ValueError as e:
print(f" [LOG] Payment failed: {e}")
raise # Re-raise the same exception
print("Payment processing:\n")
try:
result = handle_payment(100)
print(f"Result: {result}")
except ValueError as e:
print(f"Main caught: {e}")
print()
try:
result = handle_payment(-50)
print(f"Result: {result}")
except ValueError as e:
print(f"Main caught: {e}")
# Cleanup and re-raise
def database_operation(query):
connection = "DB_CONNECTION"
print(f" Opened: {connection}")
try:
if "invalid" in query:
raise RuntimeError(f"Bad query: {query}")
print(f" Executed: {query}")
return "RESULT"
except RuntimeError:
print(f" Error occurred, cleaning up...")
print(f" Closed: {connection}")
raise # Re-raise after cleanup
finally:
# This runs regardless
pass
print("\nDatabase operations:")
try:
result = database_operation("SELECT * FROM users")
print(f" Success: {result}\n")
except RuntimeError as e:
print(f" Failed: {e}\n")
try:
result = database_operation("invalid syntax")
print(f" Success: {result}")
except RuntimeError as e:
print(f" Failed: {e}")
# Conditional re-raise
def validate_and_parse(data):
try:
value = int(data)
if value < 0:
raise ValueError("Value must be non-negative")
return value
except ValueError as e:
if "invalid literal" in str(e):
# Handle parse errors locally
print(f" Parse error, using default")
return 0
else:
# Re-raise validation errors
raise
print("\nValidation and parsing:")
test_data =
for data in test_data:
try:
result = validate_and_parse(data)
print(f" '{data}' → {result}")
except ValueError as e:
print(f" '{data}' → Error: {e}")
# Transform and re-raise
def risky_operation():
items = [1, 2, 3]
return items[10] # IndexError
def wrapped_operation():
try:
return risky_operation()
except IndexError as e:
print(f" [LOG] IndexError: {e}")
# Could transform exception type here
raise RuntimeError("Operation failed") from e
print("\nWrapped operation:")
try:
wrapped_operation()
except RuntimeError as e:
print(f" Caught RuntimeError: {e}")
print(f" Caused by: {e.__cause__}")
if __name__ == "__main__":
main()
# Re-raising exceptions after logging
def main():
# Log and re-raise
def process_payment(amount):
if amount <= 0:
raise ValueError("Amount must be positive")
print(f" Processing payment: ${amount}")
return {"status": "success", "amount": amount}
def handle_payment(amount):
try:
return process_payment(amount)
except ValueError as e:
print(f" [LOG] Payment failed: {e}")
raise # Re-raise the same exception
print("Payment processing:\n")
try:
result = handle_payment(100)
print(f"Result: {result}")
except ValueError as e:
print(f"Main caught: {e}")
print()
try:
result = handle_payment(-50)
print(f"Result: {result}")
except ValueError as e:
print(f"Main caught: {e}")
# Cleanup and re-raise
def database_operation(query):
connection = "DB_CONNECTION"
print(f" Opened: {connection}")
try:
if "invalid" in query:
raise RuntimeError(f"Bad query: {query}")
print(f" Executed: {query}")
return "RESULT"
except RuntimeError:
print(f" Error occurred, cleaning up...")
print(f" Closed: {connection}")
raise # Re-raise after cleanup
finally:
# This runs regardless
pass
print("\nDatabase operations:")
try:
result = database_operation("SELECT * FROM users")
print(f" Success: {result}\n")
except RuntimeError as e:
print(f" Failed: {e}\n")
try:
result = database_operation("invalid syntax")
print(f" Success: {result}")
except RuntimeError as e:
print(f" Failed: {e}")
# Conditional re-raise
def validate_and_parse(data):
try:
value = int(data)
if value < 0:
raise ValueError("Value must be non-negative")
return value
except ValueError as e:
if "invalid literal" in str(e):
# Handle parse errors locally
print(f" Parse error, using default")
return 0
else:
# Re-raise validation errors
raise
print("\nValidation and parsing:")
test_data =
for data in test_data:
try:
result = validate_and_parse(data)
print(f" '{data}' → {result}")
except ValueError as e:
print(f" '{data}' → Error: {e}")
# Transform and re-raise
def risky_operation():
items = [1, 2, 3]
return items[10] # IndexError
def wrapped_operation():
try:
return risky_operation()
except IndexError as e:
print(f" [LOG] IndexError: {e}")
# Could transform exception type here
raise RuntimeError("Operation failed") from e
print("\nWrapped operation:")
try:
wrapped_operation()
except RuntimeError as e:
print(f" Caught RuntimeError: {e}")
print(f" Caused by: {e.__cause__}")
if __name__ == "__main__":
main()
Bare raise in except block re-raises the current exception.
Exception chaining
Preserve original exception when raising a new one.
# Exception chaining with 'from'
def main():
# Chain exceptions with 'from'
def load_config():
config_data = {"port": "invalid"}
try:
port = int(config_data["port"])
return port
except ValueError as e:
# Chain new exception from original
raise RuntimeError("Config load failed") from e
print("Exception chaining:\n")
try:
port = load_config()
print(f"Port: {port}")
except RuntimeError as e:
print(f"Exception: {e}")
print(f"Caused by: {e.__cause__}")
print(f"Cause type: {type(e.__cause__).__name__}")
# Multiple layers of chaining
def parse_number(text):
try:
return int(text)
except ValueError as e:
raise TypeError("Not a number") from e
def process_input(text):
try:
num = parse_number(text)
return num * 2
except TypeError as e:
raise RuntimeError("Processing failed") from e
print("\nMulti-layer chaining:")
try:
result = process_input("abc")
print(f"Result: {result}")
except RuntimeError as e:
print(f"Final exception: {e}")
print(f"Direct cause: {e.__cause__}")
print(f"Root cause: {e.__cause__.__cause__}")
# Suppress exception chaining
def operation_with_fallback():
try:
result = 10 / 0
except ZeroDivisionError:
# Suppress chain with 'from None'
raise ValueError("Invalid operation") from None
print("\nSuppressed chain:")
try:
operation_with_fallback()
except ValueError as e:
print(f"Exception: {e}")
print(f"Has cause: {e.__cause__ is not None}")
# Practical example: data pipeline
def fetch_data(source):
if source == "database":
raise ConnectionError("Database unreachable")
return None
def transform_data(data):
if data is None:
raise ValueError("No data to transform")
return data.upper()
def save_results(data):
if not data:
raise IOError("Cannot save empty data")
return "SAVED"
def run_pipeline(source):
try:
try:
data = fetch_data(source)
except ConnectionError as e:
raise RuntimeError("Fetch stage failed") from e
try:
transformed = transform_data(data)
except ValueError as e:
raise RuntimeError("Transform stage failed") from e
try:
result = save_results(transformed)
except IOError as e:
raise RuntimeError("Save stage failed") from e
return result
except RuntimeError:
raise
print("\nPipeline execution:")
try:
run_pipeline("database")
except RuntimeError as e:
print(f"Pipeline failed: {e}")
if e.__cause__:
print(f" Root cause: {type(e.__cause__).__name__}: {e.__cause__}")
# Check exception context
def analyze_exception(exc):
print(f"\nException analysis:")
print(f" Type: {type(exc).__name__}")
print(f" Message: {exc}")
if exc.__cause__:
print(f" Has cause: Yes")
print(f" Cause: {type(exc.__cause__).__name__}: {exc.__cause__}")
else:
print(f" Has cause: No")
try:
raise ValueError("Top level") from TypeError("Bottom level")
except ValueError as e:
analyze_exception(e)
if __name__ == "__main__":
main()
raise NewError() from original links exceptions together.
Exercise: practical.py
Build a validation system with proper exception raising