Python 3 - Exceptions
Python 3 - Exception Handling
Section titled “Python 3 - Exception Handling”Errors are inevitable in programming. Python provides robust mechanisms for dealing with errors that occur during program execution. These unexpected events are called exceptions.
Two key features for managing errors and debugging are:
- Exception Handling: Using
try,except,else, andfinallyblocks to gracefully manage runtime errors. - Assertions: Using the
assertstatement for internal self-checks during development and testing.
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 a situation it cannot cope with (e.g., dividing by zero, accessing a non-existent file, using an incorrect index), it raises an exception.
An exception is represented by a Python object, typically an instance of a class derived from the base Exception class. If an exception is raised and not handled by the program, the program terminates and prints a traceback, which shows the sequence of calls that led to the error.
Common Built-in Exceptions
Section titled “Common Built-in Exceptions”Python has many built-in exceptions for common errors. Here are some frequently encountered ones:
| Exception Name | Description |
|---|---|
Exception | The base class for most user-defined and built-in exceptions (excluding system-exiting ones). |
AttributeError | Raised when an attribute reference or assignment fails (e.g., obj.nonexistent_method()). |
EOFError | Raised when the input() function hits an end-of-file condition without reading any data. |
ImportError | Raised when an import statement fails to find the module or a name within the module. |
IndexError | Raised when a sequence subscript (index) is out of range (e.g., my_list[10] for a list with 5 elements). |
KeyError | Raised when a dictionary key is not found (e.g., my_dict['nonexistent_key']). |
KeyboardInterrupt | Raised when the user hits the interrupt key (usually Ctrl+C). |
NameError | Raised when a local or global name (variable, function) is not found. |
OSError | Base class for OS-related errors (e.g., file not found, permission denied). Subclasses like FileNotFoundError, PermissionError provide more specifics. |
SyntaxError | Raised by the parser when a syntax error is encountered. |
IndentationError | Subclass of SyntaxError. Raised when indentation is incorrect. |
TypeError | Raised when an operation or function is applied to an object of an inappropriate type (e.g., 'hello' + 5). |
ValueError | Raised when an operation or function receives an argument of the correct type but an inappropriate value (e.g., int('abc')). |
ZeroDivisionError | Raised when the second argument of a division or modulo operation is zero. |
Handling Exceptions: try and except
Section titled “Handling Exceptions: try and except”To handle exceptions gracefully, you place code that might raise an exception inside a try block. Following the try block, you add one or more except blocks to specify how to handle particular types of exceptions.
Syntax
Section titled “Syntax”try: # Code that might raise an exception # ... suspicious operations here ... passexcept SpecificExceptionType1: # Code to run if SpecificExceptionType1 occurs passexcept (SpecificExceptionType2, SpecificExceptionType3): # Code to run if either Type2 or Type3 occurs passexcept AnotherExceptionType as e: # Code to run if AnotherExceptionType occurs # 'e' holds the exception object (contains details) print(f"An error occurred: {e}")except Exception as e: # Catch any other exception derived from Exception (use cautiously) print(f"An unexpected error occurred: {e}")Key points:
- Python tries to execute the code in the
tryblock. - If an exception occurs, Python looks for a matching
exceptblock. - The first matching
exceptblock is executed. - If no matching
exceptblock is found, the exception propagates up the call stack (potentially terminating the program). - Using a bare
except:(without specifying an exception type) is generally discouraged as it catches all exceptions, including system ones likeSystemExitorKeyboardInterrupt, making it hard to debug or stop the program. - Catching specific exceptions is preferred.
Example: Handling Division by Zero and Invalid Input
Section titled “Example: Handling Division by Zero and Invalid Input”#!/usr/bin/env python3
try: numerator_str = input("Enter the numerator: ") denominator_str = input("Enter the denominator: ")
numerator = int(numerator_str) denominator = int(denominator_str)
result = numerator / denominator print(f"The result is: {result}")
except ValueError: print("Error: Please enter valid integers for numerator and denominator.")except ZeroDivisionError: print("Error: Cannot divide by zero.")except Exception as e: print(f"An unexpected error occurred: {e}")
print("Execution continues after the try-except block.")Try running this code with different inputs (e.g., ‘10’ and ‘2’, ‘10’ and ‘0’, ‘10’ and ‘abc’) to see how the different except blocks handle the errors.
The else Clause
Section titled “The else Clause”You can optionally include an else block after the except block(s). The code in the else block executes only if the try block completes without raising any exceptions.
This is useful for code that should run only when the try block succeeds, separating it from the potentially error-prone code.
try: # Risky operations risky_operation()except SomeError: print("Handling SomeError...")else: # This runs ONLY if no exceptions occurred in the try block print("Operation successful, proceeding...") perform_next_step()Example: File Handling with else
Section titled “Example: File Handling with else”#!/usr/bin/env python3
filename = "my_data.txt"
try: # Using 'with' ensures the file is closed even if errors occur with open(filename, 'r') as f: content = f.read()except FileNotFoundError: print(f"Error: File '{filename}' not found.")except OSError as e: print(f"Error reading file '{filename}': {e}")else: # Process content only if file was opened and read successfully print(f"File '{filename}' read successfully.") # print(f"Content: {content[:50]}...") # Process the contentThe finally Clause
Section titled “The finally Clause”An optional finally block can be added. The code within the finally block is always executed, 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.
The finally clause is typically used for cleanup actions that must happen under all circumstances, such as closing files or network connections, releasing locks, etc.
try: # Operations, potentially including resource allocation f = open("important_file.txt", "w") f.write("Some data") # Maybe an error occurs here: result = 10 / 0except ZeroDivisionError: print("Caught division by zero.")finally: # This cleanup code ALWAYS runs print("Ensuring file is closed.") if 'f' in locals() and not f.closed: f.close()Note: While finally is essential for cleanup, modern Python often uses context managers (the with statement) for resources like files, which automatically handle cleanup, making explicit finally blocks less common for this specific purpose. The example above demonstrates the concept; using with open(...) is generally preferred for file I/O.
A try statement can have except clauses and a finally clause. The order is try -> except(s) -> else -> finally.
Raising Exceptions
Section titled “Raising Exceptions”You can deliberately raise an exception using the raise statement. This is useful when you detect an error condition in your own code or want to re-raise an exception after catching it.
Syntax
Section titled “Syntax”# Raise a specific exception type with a messageraise ValueError("Invalid input value provided")
# Raise an instance of an exception classerror_obj = TypeError("Incompatible types")raise error_obj
# Re-raise the last exception that was active (often used in except blocks)try: do_something()except Exception as e: print("Logging the error...") # Perform some logging or partial handling raise # Re-raise the original exception 'e'Example: Validating Input
Section titled “Example: Validating Input”#!/usr/bin/env python3
def calculate_area(width, height): if not isinstance(width, (int, float)) or not isinstance(height, (int, float)): raise TypeError("Width and height must be numeric.") if width <= 0 or height <= 0: raise ValueError("Width and height must be positive.") return width * height
try: area = calculate_area(10, 5) print(f"Area: {area}")
area = calculate_area(10, -5) # area = calculate_area(10, "abc") # Uncomment to test TypeError print(f"Area: {area}")
except (ValueError, TypeError) as e: print(f"Error calculating area: {e}")Output:
Area: 50Error calculating area: Width and height must be positive.User-Defined Exceptions
Section titled “User-Defined Exceptions”You can create your own custom exception types by defining new classes that inherit from Exception (or a more specific built-in exception class). This allows you to create more meaningful error types specific to your application or library.
#!/usr/bin/env python3
# Define a custom exception classclass InsufficientFundsError(Exception): """Custom exception for when an account has insufficient funds.""" def __init__(self, balance, amount_needed): self.balance = balance self.amount_needed = amount_needed message = f"Attempted to withdraw {amount_needed}, but only {balance} available." super().__init__(message) # Call the base Exception constructor
# Example usageaccount_balance = 100withdrawal_amount = 150
try: if withdrawal_amount > account_balance: raise InsufficientFundsError(account_balance, withdrawal_amount) else: account_balance -= withdrawal_amount print(f"Withdrawal successful. New balance: {account_balance}")except InsufficientFundsError as e: print(f"Transaction Failed: {e}") # You can access custom attributes if needed: # print(f"Current balance: {e.balance}, Amount needed: {e.amount_needed}")Output:
Transaction Failed: Attempted to withdraw 150, but only 100 available.Assertions
Section titled “Assertions”Assertions (assert) are primarily used during development and testing as internal sanity checks. They verify conditions that should always be true if the program is behaving correctly.
Syntax: assert condition, [optional_message]
- If the
conditionevaluates toTrue, the assertion does nothing. - If the
conditionevaluates toFalse, Python raises anAssertionErrorwith the optional message. - Assertions can be globally disabled (for performance in production) by running Python with the
-O(optimize) flag. Therefore, they should not be used for input validation or error handling that must always occur.
Example
Section titled “Example”#!/usr/bin/env python3
def calculate_discount(price, discount_percentage): assert 0 <= discount_percentage <= 100, "Discount percentage must be between 0 and 100." discount_amount = price * (discount_percentage / 100) return price - discount_amount
# Valid callfinal_price = calculate_discount(50, 10)print(f"Final price (10% off): {final_price}")
# Invalid call - will raise AssertionError if assertions are enabledtry: final_price = calculate_discount(50, 110) print(f"Final price (110% off): {final_price}")except AssertionError as e: print(f"Assertion Failed: {e}")Output (when assertions are enabled):
Final price (10% off): 45.0Assertion Failed: Discount percentage must be between 0 and 100.