How to handle multiple exceptions in Python
Learn how to handle multiple exceptions in Python. This guide covers various methods, tips, real-world applications, and debugging common errors.

Effective exception management in Python is crucial for robust code. You can handle multiple error types gracefully within a single try...except block to prevent application crashes.
In this article, you'll learn several techniques to handle exceptions. You will find practical tips, real-world applications, and debugging advice to help you write more resilient Python code.
Basic exception handling with try-except
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero!")--OUTPUT--Cannot divide by zero!
The core idea of the try...except block is to isolate risky operations. In this example, the code attempts the division 10 / 0 inside the try statement, anticipating that it might fail.
When the expected ZeroDivisionError occurs, the program's execution jumps to the corresponding except block. This targeted approach prevents a crash and lets you handle the specific error gracefully—in this case, by printing a clear message. Your application remains stable and can continue running.
Basic approaches to handling multiple exceptions
Building on the single except block, you can expand your error-handling logic to gracefully manage several different types of exceptions at once.
Using multiple except blocks
try:
num = int("abc")
result = 10 / num
except ValueError:
print("Invalid input: not a number")
except ZeroDivisionError:
print("Cannot divide by zero!")--OUTPUT--Invalid input: not a number
Stacking multiple except blocks lets you create specific handlers for different error types. Python checks each except statement in order until it finds one that matches the error that occurred. Once a match is found, its block is executed, and any remaining except blocks are skipped.
- In this case,
int("abc")immediately raises aValueError. - The program executes the corresponding
except ValueError:block. - The code for the
ZeroDivisionErroris never reached.
Catching multiple exceptions in one except block
try:
num = int("0")
result = 10 / num
except (ValueError, ZeroDivisionError):
print("Either invalid input or division by zero")--OUTPUT--Either invalid input or division by zero
You can handle several exception types with a single block by grouping them in a tuple. This approach is useful when the same response is appropriate for different errors, which keeps your code DRY—Don't Repeat Yourself.
- The code bundles
ValueErrorandZeroDivisionErrorinto one handler. - When the division by zero occurs, Python sees that
ZeroDivisionErroris in the tuple. - As a result, it executes the shared logic inside that block.
Using exception hierarchies
try:
with open("nonexistent.txt", "r") as file:
content = file.read()
except OSError as e:
print(f"OS error occurred: {e}")--OUTPUT--OS error occurred: [Errno 2] No such file or directory: 'nonexistent.txt'
Python's exceptions are organized in a hierarchy, where more specific errors inherit from general ones. This structure allows you to catch a broad category of exceptions using a single parent class.
- In this example, attempting to open a nonexistent file raises a
FileNotFoundError. - Since
FileNotFoundErroris a subclass ofOSError, theexcept OSErrorblock successfully catches it.
This is a powerful way to handle a whole family of related errors—like file issues or permission problems—without listing each one separately.
Advanced exception handling techniques
Beyond simply catching errors, you can use finally and else clauses, create custom exceptions, and leverage context managers for even more robust code.
Using finally and else clauses
try:
number = int("42")
except ValueError:
print("Conversion failed")
else:
print(f"Conversion successful: {number}")
finally:
print("This code always executes")--OUTPUT--Conversion successful: 42
This code always executes
The else and finally clauses give you more control over your code's execution flow. The else block runs only when the try block completes without raising an exception. It’s the perfect spot for logic that depends on the try block's success.
- The
finallyclause, on the other hand, always executes. - It runs regardless of whether an exception occurred, making it essential for cleanup tasks like closing a database connection or releasing resources, ensuring your program remains tidy.
Creating custom exceptions
class MyCustomError(Exception):
def __init__(self, value):
self.value = value
try:
raise MyCustomError("Something went wrong")
except MyCustomError as e:
print(f"Caught custom exception: {e.value}")--OUTPUT--Caught custom exception: Something went wrong
Sometimes, built-in exceptions aren't specific enough for your application's logic. Creating your own custom exceptions makes your error handling more descriptive and meaningful, which improves code clarity.
- You define a new class that inherits from the base
Exceptionclass, likeMyCustomErrorin the example. - This lets you use the
raisekeyword to trigger your custom error when a specific condition is met. - You can then catch it with a dedicated
exceptblock, just like any standard Python error.
Using context managers for exception handling
from contextlib import contextmanager
@contextmanager
def handle_exceptions():
try:
yield
except (ValueError, ZeroDivisionError) as e:
print(f"Caught an error: {e}")
with handle_exceptions():
1 / 0--OUTPUT--Caught an error: division by zero
Context managers offer a clean, reusable way to handle exceptions. With the @contextmanager decorator, you can turn a simple generator function into an error-handling wrapper for use with a with statement.
- The
handle_exceptionsfunction contains atry...exceptblock that surrounds theyieldkeyword. - The code inside the
withblock executes at the point of theyield. - If an error occurs, it’s caught by the
exceptclause within the context manager, which keeps your main logic uncluttered.
Move faster with Replit
Replit is an AI-powered development platform that transforms natural language into working applications. You can describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.
For the exception handling techniques we've explored, Replit Agent can turn them into production-ready tools. You can use it to build applications that are resilient to errors from the start.
- Build a data validation utility that cleans user input, gracefully handling
ValueErrorfor non-numeric entries and custom errors for out-of-range values. - Create a batch file processor that reads data from multiple sources, automatically skipping missing files with
FileNotFoundErrorand logging permission issues withOSError. - Deploy an interactive web calculator that prevents crashes from invalid inputs or division by zero by catching both
ValueErrorandZeroDivisionError.
Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically, all in your browser. Try Replit Agent to see how quickly you can turn your concepts into working applications.
Common errors and challenges
Even with the right tools, you can run into pitfalls like overly broad exception clauses, lost tracebacks, and broken loops.
Avoiding bare except clauses for better debugging
It’s tempting to use a bare except: clause to catch any and all errors, but this is a risky habit. This approach doesn't just catch standard application errors—it also traps system-level exceptions like SystemExit or KeyboardInterrupt, which can prevent your program from exiting properly. You might not even be able to stop it with Ctrl+C.
For clearer debugging, always specify the exception types you expect to handle. If you need a general fallback, use except Exception:, which catches most standard errors but wisely ignores the ones that should terminate your program.
Properly preserving tracebacks when re-raising exceptions
Sometimes you need to catch an exception, perform an action like logging, and then re-raise it to be handled elsewhere. A common mistake is to use raise e after catching an exception as e. This creates a new exception, which unfortunately discards the original traceback—the detailed error history that shows where the problem started.
To preserve the full context, use a bare raise statement on its own. This command re-raises the original exception with its complete traceback intact, making it much easier to debug complex issues that span multiple function calls.
Handling exceptions in loops correctly
When processing a collection of items in a loop, a single unhandled exception can halt the entire operation. If your try...except block wraps the whole loop, the first error breaks you out of it completely, leaving the remaining items unprocessed.
A more resilient pattern is to place the try...except block inside the loop. This allows you to handle an error for a single item—perhaps by logging it and moving on—while the loop continues to process the rest of the collection. This is especially useful for tasks like batch processing files or API requests, where you don't want one bad entry to stop everything.
Avoiding bare except clauses for better debugging
Using a bare except: clause makes debugging difficult because it hides the specific error. It’s hard to know whether you're dealing with a ValueError from bad input or a ZeroDivisionError from the calculation itself. The following code demonstrates this problem—no matter what goes wrong, you get the same generic message.
try:
value = int(input("Enter a number: "))
result = 100 / value
except: # Bare except - catches ALL exceptions
print("An error occurred")
If the input is non-numeric, a ValueError is raised. If it's "0", a ZeroDivisionError occurs. The bare except clause treats both identically, hiding the root cause. The following example offers a more precise approach.
try:
value = int(input("Enter a number: "))
result = 100 / value
except (ValueError, ZeroDivisionError) as e:
print(f"Error: {e}")
This improved version specifically catches both ValueError and ZeroDivisionError. By using as e, you capture the actual exception object, which holds the specific error details.
Printing the exception object e gives you a precise message, such as “division by zero.” This immediately tells you the root cause of the problem, which makes debugging much faster and more effective than relying on a generic notification.
Properly preserving tracebacks when re-raising exceptions
When you catch an error and raise a new one, you risk losing the original traceback. This makes debugging a headache because the error report points to your raise statement, not the actual source of the problem. The following code shows this in action.
def process_data(data):
try:
return data[0] / data[1]
except Exception as e:
raise ValueError("Invalid data") # Original traceback is lost
The process_data function raises a new ValueError, which overwrites the original exception's details and hides the true source of the failure. The following example demonstrates a better way to handle this without losing valuable debugging information.
def process_data(data):
try:
return data[0] / data[1]
except Exception as e:
raise ValueError(f"Invalid data: {e}") from e
The key is the raise ... from e syntax. It explicitly links the new ValueError to the original exception, e. This technique, known as exception chaining, preserves the full traceback from the initial error while adding contextual information with your new exception. This is vital when an error passes through several layers of your application, as it helps you pinpoint the original point of failure instead of just where the error was re-raised.
Handling exceptions in loops correctly
An unhandled exception inside a loop can stop the entire process, leaving subsequent items unprocessed. This is especially problematic in batch operations where you don't want one bad entry to derail everything. The following code shows how a break statement can prematurely exit the loop.
numbers = [1, 2, 0, 4, 'five', 6]
results = []
for num in numbers:
try:
results.append(10 / num)
except:
break # Exits the loop on any exception
The break statement halts the loop at the first error, when num is 0. As a result, the remaining items are skipped entirely. The following code demonstrates a more resilient way to handle this.
numbers = [1, 2, 0, 4, 'five', 6]
results = []
for num in numbers:
try:
results.append(10 / num)
except (ZeroDivisionError, TypeError):
continue # Skip only invalid items
This improved pattern places the try...except block inside the loop. When an error like a ZeroDivisionError or TypeError occurs, the continue statement simply skips to the next item.
This ensures the loop completes its run, processing all valid items without being derailed by a single bad entry. It's a resilient approach, especially useful when working with large datasets or external data where you can't guarantee every item is perfect.
Real-world applications
Armed with these patterns, you can confidently tackle real-world challenges like validating user input and designing custom error hierarchies.
Validating user input with try-except
You can use a try-except block to build robust input validators that catch both incorrect data types and values that fall outside a required range.
def parse_and_validate_age(age_str):
try:
age = int(age_str)
if age <= 0 or age > 120:
raise ValueError("Age must be between 1 and 120")
return age
except ValueError as e:
print(f"Invalid age: {e}")
return None
print(parse_and_validate_age("25"))
print(parse_and_validate_age("-5"))
print(parse_and_validate_age("abc"))
The parse_and_validate_age function shows how one try...except block can manage multiple validation rules. The code first attempts to convert the input string into an integer. If the input isn't a number, Python raises a ValueError that is immediately caught.
- If the conversion is successful, a conditional check ensures the age is within a valid range.
- If the age is invalid, the code manually triggers a new
ValueErrorusing theraisekeyword.
This pattern allows the same except block to handle both data type mismatches and logical errors gracefully.
Creating a custom exception hierarchy with class inheritance
By inheriting from a base exception class, you can create a family of custom errors that can all be caught with a single, more general except block, making your code cleaner and more organized.
class InventoryError(Exception):
pass
class ItemNotFoundError(InventoryError):
pass
class InsufficientQuantityError(InventoryError):
pass
def process_order(item_id, quantity):
inventory = {"101": 5, "102": 10}
try:
if item_id not in inventory:
raise ItemNotFoundError(f"Item {item_id} not found")
if inventory[item_id] < quantity:
raise InsufficientQuantityError(f"Only {inventory[item_id]} units available")
return f"Order processed: {quantity} units of item {item_id}"
except InventoryError as e:
return f"Order failed: {e}"
print(process_order("101", 3))
print(process_order("103", 1))
print(process_order("102", 15))
The process_order function uses custom exceptions to signal exactly what went wrong during a transaction. This creates a clear and organized way to manage different failure scenarios.
- The code raises
ItemNotFoundErrorif anitem_idisn't in the inventory. - It raises
InsufficientQuantityErrorif the requestedquantityis too high.
Both of these specific errors are caught by the single except InventoryError block. This structure allows the function to handle different inventory-related problems through one unified error path, returning a clear failure message.
Get started with Replit
Put your knowledge into practice by building a real tool. Tell Replit Agent to “build a web calculator that handles division by zero and non-numeric inputs” or “create a file parser that skips corrupted lines.”
Replit Agent writes the code, tests for errors, and deploys your app for you. Start building with Replit and bring your ideas to life.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.


.png)
.png)