Manually raising (throwing) an exception in Python
Categories:
Manually Raising (Throwing) Exceptions in Python

Learn how to explicitly raise exceptions in Python using the raise
statement, understand custom exceptions, and implement robust error handling.
In Python, exceptions are a fundamental mechanism for handling errors and unusual conditions that disrupt the normal flow of a program. While Python automatically raises many built-in exceptions (like TypeError
or ValueError
), there are scenarios where you need to manually signal an error. This is achieved using the raise
statement, allowing you to enforce constraints, validate input, or indicate specific problems within your code. Understanding how to effectively raise exceptions is crucial for writing robust, maintainable, and user-friendly applications.
The raise
Statement: Basic Usage
The raise
statement is used to trigger an exception explicitly. You can raise an instance of an existing exception class or a custom exception you define. When an exception is raised, the normal flow of execution is interrupted, and Python begins searching for an appropriate except
block to handle it. If no handler is found, the program terminates and prints a traceback.
def validate_age(age):
if not isinstance(age, int):
raise TypeError("Age must be an integer")
if age < 0 or age > 120:
raise ValueError("Age must be between 0 and 120")
return age
try:
validate_age("twenty")
except TypeError as e:
print(f"Error: {e}")
try:
validate_age(-5)
except ValueError as e:
print(f"Error: {e}")
try:
print(f"Valid age: {validate_age(30)}")
except (TypeError, ValueError) as e:
print(f"Unexpected error: {e}")
Raising built-in exceptions based on input validation
Raising Custom Exceptions
While built-in exceptions cover many common error scenarios, sometimes you need to define your own exception types to represent specific error conditions unique to your application. Custom exceptions make your code more readable and allow for more granular error handling. You define a custom exception by creating a new class that inherits from Python's base Exception
class or one of its subclasses.
class InsufficientFundsError(Exception):
"""Custom exception raised when an account has insufficient funds."""
def __init__(self, message="Insufficient funds for this transaction", balance=0, amount=0):
super().__init__(message)
self.balance = balance
self.amount = amount
class BankAccount:
def __init__(self, initial_balance):
if initial_balance < 0:
raise ValueError("Initial balance cannot be negative")
self._balance = initial_balance
def withdraw(self, amount):
if amount <= 0:
raise ValueError("Withdrawal amount must be positive")
if self._balance < amount:
raise InsufficientFundsError(
f"Attempted to withdraw {amount}, but only {self._balance} available.",
balance=self._balance,
amount=amount
)
self._balance -= amount
return self._balance
def get_balance(self):
return self._balance
# Example usage
account = BankAccount(100)
print(f"Initial balance: {account.get_balance()}")
try:
account.withdraw(150)
except InsufficientFundsError as e:
print(f"Transaction failed: {e}")
print(f"Details: Balance={e.balance}, Attempted={e.amount}")
except ValueError as e:
print(f"Withdrawal error: {e}")
try:
account.withdraw(-10)
except ValueError as e:
print(f"Withdrawal error: {e}")
Defining and raising a custom InsufficientFundsError
flowchart TD A[Start Function/Method] --> B{Condition Met?} B -- No --> C[Continue Normal Execution] B -- Yes --> D[Create Exception Instance] D --> E[Raise Exception] E --> F{Is there a 'try...except' block?} F -- No --> G[Program Terminates, Print Traceback] F -- Yes --> H[Jump to Matching 'except' Block] H --> I[Handle Exception] I --> J[Resume Execution (after 'try...except')] C --> K[End Function/Method] J --> K
Flowchart illustrating the process of raising and handling an exception
Re-raising Exceptions
Sometimes, you might catch an exception to perform some cleanup or logging, but then decide that the exception should still propagate up the call stack. This is known as re-raising an exception. You can do this by using the raise
statement without any arguments inside an except
block. This preserves the original traceback information.
import logging
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
def process_data(data):
try:
result = 100 / int(data)
return result
except ValueError as e:
logging.error(f"Invalid data type for processing: {data}. Original error: {e}")
raise # Re-raises the caught ValueError
except ZeroDivisionError as e:
logging.warning(f"Attempted division by zero with data: {data}. Original error: {e}")
raise # Re-raises the caught ZeroDivisionError
try:
process_data("abc")
except ValueError:
print("Caught re-raised ValueError in main program.")
try:
process_data("0")
except ZeroDivisionError:
print("Caught re-raised ZeroDivisionError in main program.")
print("\nDemonstrating successful execution:")
try:
print(f"Result: {process_data('5')}")
except Exception as e:
print(f"Unexpected error: {e}")
Re-raising exceptions after logging them
raise
(without arguments) is preferred over raise e
(where e
is the caught exception instance). The former preserves the original traceback, while the latter resets it to the point of the raise e
statement, potentially losing valuable debugging information.