1. Basic Exception Handling
The fundamental approach to exception handling in Python involves try-except blocks:
try:
# Your code here
except IOError:
# Handle I/O errors
except Exception as e:
# Handle other exceptions
finally:
# Cleanup, runs no matter what
Exceptions in Python follow a hierarchy. If an IOError occurs, its corresponding except block executes. Other exceptions are handled by the generic Exception handler. Understanding this hierarchy allows for more specific or broad error catching as needed.
The finally clause ensures cleanup operations execute regardless of exceptions, making it ideal for closing files or releasing resources.
2. Creating Custom Exceptions
Developing custom exceptions improves code readability and maintainability by clearly representing specific error conditions:
class ApplicationError(Exception):
pass
try:
raise ApplicationError("A specific error occurred")
except ApplicationError as e:
print(e)
3. Using Else with Try-Except
The else clause in a try-except block executes only when no exceptions are raised—a feature unique to Python:
try:
# Attempt operation
except Exception:
# Handle error
else:
# Executes if no exceptions
4. Exception Variable Assignment
The as keyword assigns exceptions to variables, providing detailed information for easier debugging:
try:
# Some operation
except Exception as error:
print(f"Error: {error}")
5. Catching Multiple Exceptions
Tuples allow catching multiple exception types in a single line, simplifying error handling:
try:
# Risky operation
except (TypeError, ValueError) as error:
# Handle both exceptions
6. Chaining Exceptions
Python enables raising new exceptions while preserving the original traceback using the from keyword, aiding in debugging complex scenarios:
try:
# Some operation
except Exception as original_error:
raise RuntimeError("Something bad happened") from original_error
Use this technique judiciously as it has pros and cons.
7. Gracefully Ignoring Exceptions
The contextlib.suppress() function elegantly ignores specific exceptions, resulting in cleaner, more readable code:
from contextlib import suppress
with suppress(FileNotFoundError):
# Operation that might not find a file
8. Utilizing Assertions
Assertions raise exceptions when conditions aren't met, but use them cautiously as they can be disabled with optimization flags:
assert condition, "Condition was not met"
Assertions raise AssertionError, which can be caught directly in except blocks.
9. Formatting Exception Details
The Traceback module prints detailed exception information, displaying complete errors to assist with debugging:
import traceback
try:
raise ValueError("An error occurred")
except:
traceback.print_exc() # Print exception information to stderr
10. Using Warnings for Non-Critical Issues
The warnings module issues warnings instead of exceptions, useful for alerting users or developers to potential problems without stopping execution:
import warnings
warnings.warn("This is a warning message", UserWarning)
11. Context Managers for Resource Handling
Context managers ensure proper resource cleanup after use. The suppress function demonstrates ignoring specific exceptions:
from contextlib import contextmanager, suppress
@contextmanager
def managed_resource():
try:
resource = "Resource"
yield resource
finally:
print("Resource cleanup")
with managed_resource() as res:
print(res)
with suppress(FileNotFoundError):
open('non_existent_file.txt', 'r') # Suppresses the FileNotFoundError
12. Exception Handling Decorators
Decorators can centralize exception handling across multiple functions, simplifying error management:
from functools import wraps
def exception_handler(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as error:
print(f"Handled exception: {error}")
return None
return wrapper
@exception_handler
def risky_function():
raise ValueError("Something went wrong")
risky_function()
13. Accessing Exception Details
sys.exc_info() retrieves detailed information about the current exception, useful for logging or further error processing:
import sys
try:
raise TypeError("An error occurred")
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
print(exc_type, exc_value)
14. Analyzing Exception Context
The inspect module analyzes exception context, particularly valuable in complex error handling scenarios:
import inspect
def current_exception():
for frame in inspect.trace():
if frame[3] == 'risky_function':
return frame[0].f_locals.get('error')
try:
risky_function()
except Exception as error:
print(current_exception())
15. Dynamic Exception Classes
The types module enables dynamic creation of exception classes, useful for generating custom exceptions based on runtime conditions:
import types
DynamicException = types.new_class('DynamicException', (Exception,))
raise DynamicException("A dynamically created exception")
16. Exploring Built-in Exceptions
The builtins module lists all available exceptions in Python, helping understand the hierarchy and variety:
import builtins
for name in dir(builtins):
obj = getattr(builtins, name)
if isinstance(obj, type) and issubclass(obj, BaseException):
print(name)
17. Custom Exception String Representation
Overriding __str__ and __repr__ methods provides more informative error messages for custom exceptions:
class MyException(Exception):
def __str__(self):
return "This is a custom message for MyException"
def __repr__(self):
return "MyException()"
raise MyException
18. Creating Non-Catchable Exceptions
Deriving from BaseException creates exceptions that won't be caught by generic except Exception blocks:
class CriticalSystemError(BaseException):
pass
try:
raise CriticalSystemError("A critical error")
except Exception as error:
print("This will not catch CriticalSystemError")
19. Graceful Interruption Handling
Catch KeyboardInterrupt and SystemExit to handle user or system-initiated shutdowns gracefully:
import sys
try:
while True:
continue
except KeyboardInterrupt:
print("User interrupted the process")
sys.exit(0)
20. Generator Resource Cleanup
GeneratorExit signals when a generator is closed. Catching it enables cleanup operations when generators are terminated:
def my_generator():
try:
yield "Hello"
except GeneratorExit:
print("Generator closing")
raise
gen = my_generator()
next(gen)
gen.close()