Try/Except
The try/except block is Python's primary mechanism for handling
exceptions. Instead of crashing when an error occurs, you can catch the exception, handle it
gracefully, and keep your program running. This is essential for building robust applications
that handle unexpected situations - from invalid user input to network failures!
Basic Try/Except
The basic structure is simple: code that might fail goes in the try block,
and the error handling code goes in the except block. When an exception occurs
in the try block, Python immediately jumps to the except block. If no exception occurs, the
except block is skipped entirely.
Click Run to execute your code
1. Python executes code in the
try block2. If an exception occurs, execution jumps to the matching
except block3. If no exception occurs, the
except block is skipped4. Either way, code after try/except continues normally
Key insight: Only lines after the exception are skipped in try block - lines before it still run!
Multiple Except Clauses
Different errors require different handling. You can have multiple except
clauses, each catching a specific exception type. Python checks them in order and runs the
first matching handler. You can also catch multiple exception types in a single clause using
a tuple.
Click Run to execute your code
Exception first, more specific handlers will never run because Exception
catches everything. Python uses the first matching handler, not the most specific one.
Accessing Exception Details
Use as to capture the exception object and access its details. Different
exception types have different attributes - FileNotFoundError has filename,
KeyError has args[0] for the missing key. You can also re-raise
exceptions after logging them.
Click Run to execute your code
str(e) - Human-readable error messagee.args - Tuple of arguments passed to exceptiontype(e).__name__ - Exception class name as stringrepr(e) - Debug representation with class namee.__context__ - Original exception if this was raised during handling another
Common Error Handling Patterns
Exception handling isn't just about catching errors - it's about handling them intelligently. These patterns appear frequently in production code: safe file reading, type conversion with defaults, retry logic for flaky operations, and user-friendly error messages.
Click Run to execute your code
Safe File Reading - Return None or default on file errorsSafe Type Conversion - Convert with fallback: safe_int("x", 0)Retry Pattern - Retry flaky operations N times before giving upUser-Friendly Messages - Convert technical errors to helpful messagesCleanup Pattern - Use finally to ensure cleanup always happens
Common Mistakes
1. Using bare except
# Wrong - catches EVERYTHING including Ctrl+C!
try:
risky_operation()
except: # Bare except - NEVER do this!
pass # Silently swallows all errors
# Wrong - too broad, hides bugs
try:
risky_operation()
except Exception:
pass # Still catches too much
# Correct - catch specific exceptions
try:
risky_operation()
except ValueError:
print("Invalid value")
except ConnectionError:
print("Network failed")
2. Catching exceptions in wrong order
# Wrong - FileNotFoundError is never reached!
try:
open("missing.txt")
except OSError:
print("OS error") # This catches FileNotFoundError too
except FileNotFoundError:
print("File not found") # NEVER RUNS - dead code!
# Correct - specific exceptions first
try:
open("missing.txt")
except FileNotFoundError:
print("File not found") # Specific first
except OSError:
print("Other OS error") # General second
3. Swallowing exceptions silently
# Wrong - error disappears, debugging nightmare!
def get_user(user_id):
try:
return database.fetch(user_id)
except:
return None # What went wrong? Nobody knows!
# Correct - at least log the error
import logging
def get_user(user_id):
try:
return database.fetch(user_id)
except DatabaseError as e:
logging.error(f"Failed to fetch user {user_id}: {e}")
return None
4. Using exceptions for flow control
# Wrong - slow and unclear, exceptions aren't for flow control
def find_item(items, target):
try:
return items.index(target)
except ValueError:
return -1
# Also wrong for dictionaries
try:
value = my_dict[key]
except KeyError:
value = default
# Correct - use built-in methods
def find_item(items, target):
return items.index(target) if target in items else -1
# Correct for dictionaries
value = my_dict.get(key, default)
5. Too much code in try block
# Wrong - too much in try, unclear what might fail
try:
data = load_config()
processed = process_data(data)
result = calculate(processed)
save_result(result)
send_notification()
except Exception as e:
print(f"Something failed: {e}") # Which operation?
# Correct - minimal try blocks, clear error handling
try:
data = load_config()
except FileNotFoundError:
data = default_config()
try:
processed = process_data(data)
result = calculate(processed)
except ValueError as e:
print(f"Invalid data: {e}")
return
save_result(result) # Let this fail if it fails
Exercise: Safe Calculator
Task: Create a calculator function that handles all possible errors gracefully.
Requirements:
- Handle division by zero
- Handle invalid number inputs
- Handle unknown operations
- Return a tuple of (success, result_or_error)
Click Run to execute your code
Show Solution
def safe_calculate(a, b, operation):
"""
Safely perform calculation with full error handling.
Returns (True, result) on success, (False, error_msg) on failure.
"""
# Convert inputs to numbers
try:
num_a = float(a)
num_b = float(b)
except (ValueError, TypeError) as e:
return (False, f"Invalid number: {e}")
# Perform operation
operations = {
'+': lambda x, y: x + y,
'-': lambda x, y: x - y,
'*': lambda x, y: x * y,
'/': lambda x, y: x / y,
'**': lambda x, y: x ** y,
}
if operation not in operations:
return (False, f"Unknown operation: {operation}")
try:
result = operations[operation](num_a, num_b)
return (True, result)
except ZeroDivisionError:
return (False, "Cannot divide by zero")
except OverflowError:
return (False, "Result too large")
# Test the calculator
test_cases = [
(10, 5, '+'), # Normal addition
(10, 0, '/'), # Division by zero
('abc', 5, '+'), # Invalid input
(10, 5, '%'), # Unknown operation
(2, 1000, '**'), # Large exponent (might overflow)
('10', '3', '-'), # String numbers (should work)
]
print("=== Safe Calculator Tests ===\n")
for a, b, op in test_cases:
success, result = safe_calculate(a, b, op)
status = "OK" if success else "ERR"
print(f"{a} {op} {b} => [{status}] {result}")
Summary
- try/except: Code in try runs; if it fails, except handles the error
- Specific exceptions: Always catch specific types, not bare except
- Multiple except: Handle different errors differently, specific first
- as keyword: Capture exception:
except ValueError as e - Exception attributes:
str(e),e.args,type(e).__name__ - Tuple syntax:
except (TypeError, ValueError)for multiple types - Re-raise: Use bare
raiseto re-raise after logging - Keep try blocks small: Only wrap code that might fail
- Don't swallow errors: At minimum, log what went wrong
What's Next?
Now you know how to catch exceptions, but what about cleanup code that must always run?
And what if you want to do something only when no exception occurs? Next, we'll learn about
finally for guaranteed cleanup and else for success-only code -
completing your exception handling toolkit!
Enjoying these tutorials?