Exception Handling
Exceptions are unexpected events that have occurred in the execution of a program, like an alarm that goes off when unexpected events happen while running code.
These events could be caused by several factors:
- Logical Errors: i.e., typo, missing data, wrong data, etc.
- Unforeseen Events: trying to open a file that doesn’t exist.
1try:
2 # Code that might cause an error (like accessing a missing file)
3except FileNotFoundError:
4 # Do this if a FileNotFoundError happens
5except TypeError:
6 # Do this if a TypeError happens
7else:
8 # Do this if NO exception happened in the 'try' block
9finally:
10 # This ALWAYS runs, error or no error
Common Exception Types
Class | Description |
---|---|
Exception | The base class for most error types. |
AttributeError | Raised when attempting to access a non-existent attribute of an object (e.g., calling a method the object does not have). |
EOFError | Occurs when input() is called without an available line to read, often indicating an unexpected end of file. |
IOError | Signals an error related to input/output operations, such as file handling. Common causes include attempting to open a non-existent file or insufficient permissions. |
IndexError | Raised when accessing a sequence, a list, with an index that is out of range. |
KeyError | Occurs when attempting to access a dictionary key that does not exist. |
KeyboardInterrupt | Raised when the user interrupts program execution, typically by pressing Ctrl+C |
NameError | Indicates that a local or global name (variable, function, etc.) is not found. Often due to typos or referencing names before definition. |
StopIteration | Raised by an iterators __next__() method to signal the end of the iteration. |
TypeError | Occurs when an operation or function is applied to an object of an inappropriate type, such as attempting to add a string to an integer. |
ValueError | Raised when an operation or function receives an argument of the correct type but with an inappropriate value. For instance, attempting to compute the square root of a negative number. |
ZeroDivisionError | Signals an attempt to divide by zero, an undefined mathematical operation. |
Raising an Exception
raise
: This keyword throws the exception, halting normal execution.ValueError
: This is a built-in exception type that’s perfect for signaling inappropriate values."x cannot be negative"
: This message explains the problem, helping you debug later.
1def calculate_square_root(x):
2 if x < 0:
3 raise ValueError(f"{x} cannot be negative")
4 # ... rest of the logic goes here ...
Why is this useful?
- Early Detection: Find problems quickly.
- Clean Code: Separate error handling from regular logic.
- Maintainability: Makes your code easier to understand and fix in the future.
Custom Exceptions:
While built-in exceptions like ValueError
are useful for general cases,
custom exceptions give you the power to pinpoint very specific issues within your code,
this is useful for debugging or testing.
This makes debugging significantly easier. Furthermore, as your projects expand,
custom exceptions become invaluable for organizing and categorizing errors, leading to improved code structure and maintainability.
This targeted approach extends to error handling itself: you can write except
blocks designed to catch only your custom exceptions,
enabling more precise and effective error responses.
- Inheritance: Always inherit from
Exception
(directly or indirectly) to leverage Python’s exception system.
1class MyCustomError(Exception):
2 # Can be empty for simple cases
3 pass
- Constructor (
__init__
): Optional, but useful for storing extra error information.
1class InvalidAgeError(Exception):
2 def __init__(self, age, message="Age must be between 0 and 120"):
3 super().__init__(message)
4 self.age = age
Example
1class InsufficientFundsError(Exception):
2 def __init__(self, balance, amount):
3 super().__init__(f"Insufficient funds: Balance={balance}, Attempted withdrawal={amount}")
4 self.balance = balance
5 self.amount = amount
6
7def withdraw(balance, amount):
8 if amount > balance:
9 raise InsufficientFundsError(balance, amount)
10 return balance - amount
11
12try:
13 new_balance = withdraw(100, 150)
14except InsufficientFundsError as e:
15 # Insufficient funds: Balance=100, Attempted withdrawal=150
16 print(e)
17 print("Please try a smaller amount.")
18else:
19 print(f"Withdrawal successful. New balance: {new_balance}")
Best Practices:
- Avoiding overly broad exception catches (e.g., catching
Exception
). - Providing informative error messages to aid debugging.
- Using exceptions appropriately.
Notes:
try
: Wrap risky code. Could crash your program.except
: Handle specific errors if they occur.else
: Run code if no errors.finally
: Clean up, always runs.raise
: throws an exception.