By the end of this lesson, you will be able to write Python code that detects errors, handles them gracefully without crashing, and cleans up resources using try, except, finally, and custom exceptions.
What it is
Exception handling is a mechanism in Python that allows a program to respond to runtime errors (exceptions) rather than terminating abruptly. When an error occurs, Python raises an exception object. You can "catch" this exception using atry block and define how to handle it in an except block. This separates normal logic from error-handling logic, making code more robust and readable. Key terms include raising (triggering an error), catching (handling an error), and stack trace (the report of where the error occurred).
Why it matters
- Prevents Crashes: A single unhandled error stops the entire program; handling it allows execution to continue or exit cleanly.
- User Experience: Instead of showing confusing technical stack traces, you can display friendly messages like "File not found."
- Resource Management: Ensures files are closed or network connections are terminated even if an error occurs during processing.
- Debugging Aid: Custom exceptions allow you to create specific error types for your application logic, making debugging easier.
Syntax or steps
The basic structure involves three blocks: 1.try: Contains the code that might raise an exception.
2. except: Contains the code to run if a specific exception occurs. You can have multiple except blocks for different error types.
3. finally: Optional. Contains code that runs no matter what happens (success or failure), typically used for cleanup.
You can also use raise to manually trigger an exception, often within a custom exception class.
Example
class InsufficientFundsError(Exception):
"""Custom exception for bank withdrawals."""
pass
def withdraw(balance, amount):
try:
if amount <= 0:
raise ValueError("Withdrawal amount must be positive.")
if amount > balance:
raise InsufficientFundsError(f"Cannot withdraw {amount}. Balance is {balance}.")
new_balance = balance - amount
print(f"Withdrawal successful. New balance: {new_balance}")
return new_balance
except ValueError as ve:
print(f"Input Error: {ve}")
except InsufficientFundsError as ise:
print(f"Transaction Failed: {ise}")
finally:
print("--- Transaction attempt finished ---")
# Test cases
withdraw(100, 50) # Success
withdraw(100, 150) # Custom Exception
withdraw(100, -10) # Built-in Exception
Explanation:
- We define InsufficientFundsError inheriting from Exception.
- Inside withdraw, we check conditions. If invalid, we raise specific exceptions.
- The except blocks catch these specific errors and print user-friendly messages.
- The finally block prints a status message regardless of whether the withdrawal succeeded or failed.
Common mistakes
- Bare Except: Using just
except:catches *all* exceptions, including system-exiting ones likeKeyboardInterrupt. Always specify the exception type (e.g.,except ValueError:) unless you have a very good reason. - Hiding Errors: An empty
exceptblock (pass) silently ignores errors, making bugs impossible to find. At minimum, log the error. - Catching Too Broadly: Catching
Exceptioninstead of specific types (likeFileNotFoundError) can mask unexpected bugs. Catch only what you know how to handle. - Forgetting Cleanup: Not using
finallyor context managers (with) when opening files can lead to resource leaks if an error occurs mid-operation.
When to use it
Compare standard control flow with exception handling:| Scenario | Use Control Flow (if/else) | Use Exceptions (try/except) |
|---|---|---|
| Expected condition (e.g., checking if a key exists) | Yes | No |
| Unexpected error (e.g., file missing, network down) | No | Yes |
| Validation logic (e.g., input format) | Often | If part of API contract |
if statements for normal branching logic.
Practice
Guided Exercise: Write a functiondivide(a, b) that returns a / b. Handle the case where b is zero by printing "Cannot divide by zero" and returning None.
Challenge: Modify the function to also handle non-numeric inputs by catching TypeError.
Solution Hint: Use except ZeroDivisionError: and except TypeError: blocks.
Quick check
Question: What is the purpose of thefinally block?
Answer: It executes code that must run regardless of whether an exception was raised or caught, typically used for releasing resources like closing files or database connections.
Summary
Python's exception handling allows programs to react to errors gracefully rather than crashing. By usingtry, except, and finally, you can separate error logic from main logic, ensuring data integrity and better user experience. Remember to catch specific exceptions and avoid bare except clauses to maintain debuggable code.