Handling Exceptions in Python
Understanding Exceptions
During program execution, various errors can occur. Some stem from code bugs that require fixing, while others result from invalid user input and can be handled through input validation. Additionally, unpredictable runtime issues like network failures or file access problems constitute exceptions that must be managed to prevent program termination.
Common Exception Types
| Exception | Description |
|---|---|
AttributeError |
Accessing an attribute that doesn't exist on an object |
IOError |
Input/output operations fail, such as unable to open a file |
ImportError |
Module or package cannot be imported due to path or name issues |
IndentationError |
Code is not properly aligned |
IndexError |
List index out of range (e.g., accessing element 5 in a 3-element list) |
KeyError |
Dictionary key not found |
KeyboardInterrupt |
User presses Ctrl+C |
NameError |
Using a variable before assignment |
SyntaxError |
Invalid Python syntax |
TypeError |
Argument type doesn't match expected type |
UnboundLocalError |
Accessing a local variable before initialization (often from a same-named global) |
ValueError |
Argumant value is invalid even if type is correct |
Exception Handling with try...except...else
The try...except...else construct detects errors in the try block and allows except to capture and handle them.
try:
result = 100 / 0
except ZeroDivisionError as e:
print(f'Error: {e}')
How Execution Works
- If an exception occurs in the
tryblock, Python searchse for the first matchingexceptclause and executes it - If no matching
exceptis found, the exception propagates to the outertryblock or terminates the program - If no exception occurs, the
elseblock executes (if present), then execution continues normally
Handling Multiple Exception Types
def divide_values(numerator, denominator):
try:
quotient = numerator / denominator
except ZeroDivisionError as err:
print(f'Division operation failed: {err}')
except TypeError as err:
print(f'Invalid data type provided: {err}')
else:
print('Division completed successfully')
Generic Exception Handling
When the specific exception type is unknown, a bare except clause catches all exceptions:
try:
result = 50 / 0
except:
print('An error occurred, but execution continues')
Grouping Multiple Exceptions
data = [10, 20, 30]
try:
print(data[15])
except (NameError, ZeroDivisionError, IndexError):
print('An expected error type occurred')
Exception Inheritance Hierarchy
All exceptions inherit from BaseException. When BaseException is caught first, all subclasses are ignored:
try:
value = 1 / 0
except ZeroDivisionError:
print('Caught specific: ZeroDivisionError')
except BaseException:
print('Caught parent: BaseException')
Reversing the order changes behavior entirely:
try:
value = 1 / 0
except BaseException:
print('Caught parent: BaseException')
except ZeroDivisionError:
print('This code never executes')
Cross-Layer Exception Catching
Exceptions propagate through nested function calls, allowing outer try blocks to handle them:
def innermost():
return 10 / 0
def middle():
innermost()
def outermost():
middle()
try:
outermost()
except ZeroDivisionError:
print('Exception caught despite originating in nested function')
The finally Clause
The finally block executes regardless of whether an exception occurred:
def read_file_content(filepath):
handle = None
try:
handle = open(filepath, 'r')
contents = handle.read()
return contents
except FileNotFoundError:
print(f'File not found: {filepath}')
finally:
if handle:
handle.close()
print('Cleanup complete')
Using Assertions
The assert statement validates conditions and raises AssertionError when the condition is false:
def calculate_average(values, count):
assert count != 0, 'Count parameter must be non-zero'
return sum(values) / count
calculate_average([10, 20], 0)
The first argument is the condition to check. If false, execution stops and the second argument (error message) is displayed.
Best Practices
- Catch specific exceptions rather than using bare
exceptclauses - Handle exceptions at the appropriate level in your call stack
- Use
finallyfor cleanup operations like closing files or network connections - Use assertions for debugging and validating internal program state
- Avoid suppressing exceptions without proper handling