try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero")
Python try except blocks prevent your programs from crashing when errors occur. The code above catches a division by zero error and handles it gracefully instead of terminating the program. This mechanism separates normal execution flow from error handling logic, making your code more reliable and maintainable.
How python try except blocks work
Python executes the code inside the try block first. If no error occurs, the except block gets skipped entirely. When an exception happens, Python immediately stops executing the try block and jumps to the matching except handler.
try:
user_input = input("Enter a number: ")
number = int(user_input)
print(f"You entered: {number}")
except ValueError:
print("That's not a valid number")
The flow starts with requesting user input. Python then attempts to convert the input to an integer. If the user types “hello” instead of a number, the int() function raises a ValueError. The except block catches this specific error and displays a helpful message.
Catching multiple exception types in python
Real programs need to handle different error scenarios. You can specify multiple except blocks to catch distinct exception types.
try:
file = open("data.txt", "r")
content = file.read()
value = int(content)
file.close()
except FileNotFoundError:
print("File doesn't exist")
except ValueError:
print("File contains invalid data")
except PermissionError:
print("Don't have permission to read file")
Each except block targets a specific error type. When Python encounters an exception, it checks each except clause in order. The first matching handler executes, and the rest get ignored. This ordering matters when dealing with exception hierarchies.
You can also catch multiple exceptions in a single except block by using a tuple.
try:
data = fetch_api_data()
process_data(data)
except (ConnectionError, TimeoutError):
print("Network request failed")
This approach works when you want identical handling for related exceptions. Both ConnectionError and TimeoutError trigger the same response.
Accessing exception details with python try except
Exception objects carry information about what went wrong. You can capture this data using the as keyword.
try:
result = calculate_average([1, 2, "three", 4])
except TypeError as error:
print(f"Error type: {type(error).__name__}")
print(f"Error message: {str(error)}")
print(f"Error args: {error.args}")
The variable error holds the exception instance. You can inspect the error message, access the exception type, or retrieve the arguments passed when the exception was raised. This information helps with debugging and logging.
Using else clauses with python try except
The else clause executes only when the try block completes without raising any exception. This keeps success logic separate from error handling.
try:
file = open("config.json", "r")
data = json.load(file)
except FileNotFoundError:
print("Config file missing, using defaults")
data = default_config()
else:
print("Config loaded successfully")
validate_config(data)
finally:
print("Configuration process complete")
The validation code in the else block runs only after successfully loading the file. Placing this code in the try block would be risky because any errors during validation would incorrectly trigger the FileNotFoundError handler. The else clause prevents this confusion.
Implementing finally blocks for cleanup operations
The finally block always executes, regardless of whether an exception occurred. This guarantees cleanup code runs even when errors happen.
connection = None
try:
connection = database.connect()
result = connection.execute(query)
return result
except DatabaseError as error:
log_error(error)
raise
finally:
if connection:
connection.close()
Database connections must be closed properly to prevent resource leaks. The finally block ensures the connection closes whether the query succeeds, fails, or raises an exception. This pattern applies to any resource that requires cleanup, including file handles and network sockets.
Raising exceptions in python
You can trigger exceptions deliberately using the raise keyword. This helps enforce business logic and data validation rules.
def withdraw(account, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive")
if amount > account.balance:
raise ValueError("Insufficient funds")
account.balance -= amount
return account.balance
Raising exceptions communicates error conditions to calling code. The ValueError exceptions signal that something went wrong, and the error messages explain the specific problem. This approach is clearer than returning error codes or special values.
Re-raising exceptions after logging
Sometimes you want to log an error but still propagate it to higher-level code. Use a bare raise statement inside an except block.
try:
response = requests.get(api_url, timeout=5)
response.raise_for_status()
except requests.RequestException as error:
logger.error(f"API request failed: {error}")
raise
The except block logs the error for monitoring purposes. The bare raise statement then re-throws the same exception without modifying it. This preserves the original stack trace, making debugging easier.
Chaining exceptions with raise from
Exception chaining links a new exception to the original one. This creates a complete error history showing how one problem led to another.
try:
raw_data = load_from_cache()
except CacheError as error:
raise DataLoadError("Failed to load data") from error
The from keyword establishes a causal relationship between exceptions. Python’s traceback displays both the CacheError and the DataLoadError, showing the complete chain of events. This context helps identify root causes faster than seeing only the final exception.
You can suppress the automatic chaining by using from None.
try:
result = parse_json(data)
except json.JSONDecodeError:
raise ValueError("Invalid data format") from None
This shows only the ValueError in the traceback, hiding the JSONDecodeError. Use this sparingly, as it removes potentially valuable debugging information.
Handling file operations with python try except
File operations are prone to multiple failure modes. Combining exception handling with context managers creates robust file handling code.
def read_configuration(filename):
try:
with open(filename, 'r') as file:
content = file.read()
config = json.loads(content)
return config
except FileNotFoundError:
print(f"Config file {filename} not found")
return create_default_config()
except json.JSONDecodeError as error:
print(f"Invalid JSON in config file: {error}")
return create_default_config()
except PermissionError:
print(f"No permission to read {filename}")
raise
The with statement handles file closing automatically. The try except structure catches specific errors without hiding unexpected exceptions like PermissionError, which gets re-raised to alert administrators of access control issues.
Validating user input with python try except
Python try except blocks excel at handling unpredictable user input. Converting strings to numbers is a common scenario requiring validation.
def get_integer_input(prompt, min_value=None, max_value=None):
while True:
try:
value = int(input(prompt))
if min_value is not None and value < min_value:
print(f"Value must be at least {min_value}")
continue
if max_value is not None and value > max_value:
print(f"Value must be at most {max_value}")
continue
return value
except ValueError:
print("Please enter a valid integer")
except KeyboardInterrupt:
print("\nInput cancelled")
raise
The loop continues until the user provides valid input. The ValueError exception handles non-numeric input, while the range checks enforce business rules. KeyboardInterrupt gets re-raised because user cancellation should propagate to the calling code.
Working with API requests using python try except
Network operations introduce latency and failure points. Proper exception handling makes API integrations more resilient.
import requests
from requests.exceptions import RequestException, Timeout, HTTPError
def fetch_user_data(user_id, max_retries=3):
url = f"https://api.example.com/users/{user_id}"
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()
except Timeout:
print(f"Request timeout (attempt {attempt + 1}/{max_retries})")
if attempt == max_retries - 1:
raise
except HTTPError as error:
if error.response.status_code == 404:
return None
print(f"HTTP error: {error}")
raise
except RequestException as error:
print(f"Request failed: {error}")
raise
This function implements retry logic for timeout errors while immediately failing on HTTP errors. The 404 status returns None instead of raising an exception, treating missing users as a valid scenario. Other HTTP errors and connection problems propagate immediately.
Creating custom exceptions in python
Custom exceptions make your code more expressive and easier to handle. They communicate domain-specific problems clearly.
class InsufficientFundsError(Exception):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"Cannot withdraw {amount}. Balance: {balance}")
class AccountLockedError(Exception):
pass
def process_withdrawal(account, amount):
if account.is_locked:
raise AccountLockedError("Account is locked")
if amount > account.balance:
raise InsufficientFundsError(account.balance, amount)
account.balance -= amount
Custom exceptions carry contextual data. InsufficientFundsError stores both the current balance and the requested amount. Calling code can catch this specific exception and access these attributes to provide detailed error messages to users.
Best practices for python try except blocks
Keep try blocks small and focused. Only wrap code that might raise exceptions, not entire functions.
# Better approach
def process_order(order_data):
validate_order_structure(order_data)
try:
customer = Customer.objects.get(id=order_data['customer_id'])
except Customer.DoesNotExist:
raise ValueError("Customer not found")
try:
payment = process_payment(order_data['payment_info'])
except PaymentError as error:
log_payment_failure(error)
raise
create_order(customer, order_data, payment)
This approach makes error sources obvious. Each try block contains exactly the operation that might fail. Avoid catching Exception without re-raising it, as this swallows all errors including KeyboardInterrupt and SystemExit.
Never use bare except clauses except in very specific cases like logging all exceptions before re-raising them.
# Avoid this
try:
risky_operation()
except:
print("Something went wrong")
# Use this instead
try:
risky_operation()
except Exception as error:
logger.exception("Unexpected error in risky_operation")
raise
The bare except catches system exceptions that should propagate. Catching Exception explicitly makes your intent clear while still allowing program termination signals through.
Debugging exceptions in python
Python’s traceback shows the complete call stack when an exception occurs. Reading tracebacks from bottom to top reveals the error progression.
def calculate_total(items):
return sum(item['price'] * item['quantity'] for item in items)
def process_cart(cart_data):
try:
total = calculate_total(cart_data['items'])
return {'total': total, 'status': 'success'}
except KeyError as error:
return {
'total': 0,
'status': 'error',
'message': f'Missing required field: {error}',
'cart_data': cart_data
}
except TypeError as error:
return {
'total': 0,
'status': 'error',
'message': f'Invalid data type: {error}',
'cart_data': cart_data
}
Including the original data in error responses helps reproduce and fix bugs. The error dictionary contains both the exception message and the input that caused the problem.
Python exception handling turns potential crashes into managed error states. The try except mechanism lets you anticipate problems, respond appropriately, and maintain program stability. Whether validating input, accessing resources, or communicating with external services, proper exception handling separates robust code from fragile implementations.



