Python Exceptions
Python Exception Handling
Section titled “Python Exception Handling”Errors are inevitable in programming. Python provides robust mechanisms to handle runtime errors gracefully, preventing your program from crashing unexpectedly.
- Exception Handling: Using
try,except,else, andfinallyblocks to manage errors as they occur. - Assertions: Using the
assertstatement for internal self-checks during development and debugging.
What is an Exception?
Section titled “What is an Exception?”An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions. When Python encounters an error condition (like dividing by zero or trying to access a non-existent file), it creates an exception object.
If this exception object is not ‘caught’ and handled by your code, the program terminates and prints a traceback (an error message showing where the error occurred).
Common Built-in Exceptions
Section titled “Common Built-in Exceptions”Python has many built-in exceptions for common errors. Understanding these helps in writing specific except blocks. Here are a few examples:
| Exception Name | Description |
|---|---|
Exception | The base class for most built-in, non-system-exiting exceptions. |
AttributeError | Raised when an attribute reference or assignment fails (e.g., mylist.non_existent_method()). |
ImportError | Raised when an import statement fails to find the module or a name within the module. |
ModuleNotFoundError | Subclass of ImportError, raised when a module cannot be located. |
IndexError | Raised when a sequence subscript (index) is out of range (e.g., mylist[10] when list has only 3 items). |
KeyError | Raised when a dictionary key is not found (e.g., mydict['non_existent_key']). |
NameError | Raised when a local or global name (variable, function) is not found. |
TypeError | Raised when an operation or function is applied to an object of inappropriate type (e.g., '2' + 2). |
ValueError | Raised when an operation or function receives an argument of the right type but inappropriate value (e.g., int('abc')). |
ZeroDivisionError | Raised when the second argument of a division or modulo operation is zero. |
FileNotFoundError | Subclass of OSError, raised when trying to open a file that doesn’t exist. |
OSError | Base class for OS-related errors (like file not found, permission denied). |
KeyboardInterrupt | Raised when the user hits the interrupt key (usually Ctrl+C). |
Handling Exceptions: try and except
Section titled “Handling Exceptions: try and except”To handle potential errors, you place the code that might raise an exception inside a try block. You then follow it with one or more except blocks to specify how to handle different types of exceptions.
Syntax
Section titled “Syntax”try: # Code that might raise an exception # ... operations ... result = 10 / risky_variable file = open("maybe_missing.txt", "r") # ... more operations ...except ZeroDivisionError: # Handle division by zero specifically print("Error: Cannot divide by zero!") result = 0except FileNotFoundError: # Handle missing file specifically print("Error: File not found!") # Maybe provide a default or exitexcept Exception as e: # Catch any other exceptions that inherit from Exception print(f"An unexpected error occurred: {e}") # Log the error, provide a generic message, etc.
# Code here continues execution if an exception was caughtKey Points:
- Python tries to execute the code in the
tryblock. - If an exception occurs, Python stops executing the
tryblock and looks for a matchingexceptblock. - It checks the
exceptblocks in order. The first one whose type matches the raised exception (or is a base class of it) is executed. - If a matching
exceptblock is found and executed, the program continues after thetry...exceptstructure (unless theexceptblock itself raises an exception or exits). - If no matching
exceptblock is found, the exception propagates up the call stack. If uncaught, the program terminates. - Using a specific exception type (
ZeroDivisionError) is generally better than a broad one (Exception) because it allows tailored error handling and avoids accidentally catching unrelated errors. - The
as eclause captures the exception object into the variablee, allowing access to details about the error.
The else Clause
Section titled “The else Clause”You can add an optional else block after all except blocks. The code in the else block executes only if the try block completes without raising any exceptions.
Syntax
Section titled “Syntax”try: # Operations that might fail value = int(input("Enter a number: ")) result = 100 / valueexcept ValueError: print("Invalid input. Please enter a number.")except ZeroDivisionError: print("Cannot divide by zero.")else: # Executes only if try block succeeds print(f"Result is: {result}") print("Operation successful.")
print("Program continues...")The else block is useful for code that should run only when the try block was successful, separating it from the potentially risky code.
The finally Clause
Section titled “The finally Clause”An optional finally block can be added. The code in the finally block always executes, regardless of whether an exception occurred in the try block or if it was handled by an except block. It even runs if the try or except block uses return, break, or continue.
Syntax
Section titled “Syntax”file = None # Initialize file variable outside trytry: print("Attempting to open file...") file = open("my_data.txt", "w") file.write("Writing important data!") # Simulate an error potentially happening after opening # result = 10 / 0except FileNotFoundError: print("Error: Could not find the file path to write.")except Exception as e: print(f"An error occurred during writing: {e}")finally: # This block always executes for cleanup print("Entering finally block...") if file and not file.closed: print("Closing the file.") file.close() else: print("File was not opened or already closed.")
print("Program finished.")finally is typically used for cleanup actions, such as closing files or network connections, ensuring resources are released properly.
Note: A try block must have at least one except or finally clause. It can have except clauses, a finally clause, or both. If both are present, the finally clause executes last.
Argument of an Exception
Section titled “Argument of an Exception”As seen in the examples with except Exception as e:, you can capture the exception object itself. This object often contains useful information about the error.
Example:
Section titled “Example:”try: x = int('not_a_number')except ValueError as ve: print(f"Caught a ValueError!") print(f"Error details: {ve}") # Prints the standard error message associated with the exception print(f"Arguments: {ve.args}") # Exceptions store arguments, often including the messageThis produces output like:
Caught a ValueError!Error details: invalid literal for int() with base 10: 'not_a_number'Arguments: ("invalid literal for int() with base 10: 'not_a_number'",)Raising Exceptions
Section titled “Raising Exceptions”You can also deliberately raise exceptions in your own code using the raise statement. This is useful when you detect an error condition that prevents your function or method from fulfilling its contract.
Syntax
Section titled “Syntax”raise ExceptionType("Optional descriptive message about the error")Example
Section titled “Example”def check_positive(number): if number <= 0: # Raise a specific, appropriate exception raise ValueError("Input must be a positive number.") print(f"{number} is positive.")
try: check_positive(10) check_positive(-5)except ValueError as e: print(f"Error caught: {e}")Best practice is to raise specific, built-in exceptions (like ValueError, TypeError) when appropriate, or create custom exceptions for application-specific errors.
Note: Raising exceptions using strings (raise "Invalid level!") is deprecated and should not be used in modern Python.
User-Defined Exceptions
Section titled “User-Defined Exceptions”For errors specific to your application or library, you can create custom exception classes by inheriting from Exception or a more specific built-in exception class.
Example
Section titled “Example”# Define a custom exception classclass InsufficientFundsError(Exception): """Custom exception for when an account has insufficient funds.""" def __init__(self, balance, required): self.balance = balance self.required = required message = f"Attempted to withdraw {required}, but only {balance} available." super().__init__(message) # Call the base class constructor
# Example usageaccount_balance = 100withdrawal_amount = 150
try: if withdrawal_amount > account_balance: raise InsufficientFundsError(account_balance, withdrawal_amount) else: print("Withdrawal successful.") # account_balance -= withdrawal_amountexcept InsufficientFundsError as e: print(f"Transaction Failed: {e}") # Access custom attributes if needed: # print(f"Current Balance: {e.balance}, Amount Required: {e.required}")Custom exceptions make your error handling more specific and informative.
Assertions in Python
Section titled “Assertions in Python”An assertion is a debugging aid that tests a condition. If the condition is True, nothing happens. If the condition is False, the assert statement raises an AssertionError exception with an optional message.
Assertions are primarily used to check for internal errors or violations of assumptions (programmer errors), not for expected error conditions (like invalid user input or missing files, which should be handled by try...except).
Syntax
Section titled “Syntax”assert condition, "Optional message if condition is False"Example
Section titled “Example”def calculate_discount(price, discount_percentage): # Pre-conditions check: ensure inputs are valid for the logic assert price >= 0, "Price cannot be negative" assert 0 <= discount_percentage <= 100, "Discount must be between 0 and 100"
discount_amount = price * (discount_percentage / 100) final_price = price - discount_amount
# Post-condition check: ensure result makes sense assert 0 <= final_price <= price, "Final price calculation seems wrong" return final_price
# Valid callprint(f"Discounted price: {calculate_discount(100, 10)}")
# Invalid call - will raise AssertionErrortry: print(calculate_discount(100, 110))except AssertionError as e: print(f"Assertion Failed: {e}")Important Note: Assertions can be globally disabled in Python by running the interpreter with the -O (optimize) flag (python -O your_script.py). Therefore, they should not be used for error handling logic that must always be present.