Error Handling
## Learning Objectives
- Understand Python exceptions
- Use try/except/finally
- Raise custom exceptions
- Handle specific error types
## Exceptions vs Syntax Errors
### Syntax Errors
```python
# These are caught before the program runs
if True
print("Hello")
# File "", line 1
# if True
# ^
# SyntaxError: invalid syntax
```
### Exceptions
```python
# These occur during runtime
print(10 / 0)
# Traceback (most recent call last):
# File "", line 1, in
# ZeroDivisionError: division by zero
```
## Basic try/except
### Simple Example
```python
try:
result = 10 / 0
except ZeroDivisionError:
result = 0
print(result) # 0
```
### Catching Multiple Exceptions
```python
try:
value = int(input("Enter a number: "))
result = 10 / value
except ValueError:
print("That's not a valid number!")
except ZeroDivisionError:
print("Can't divide by zero!")
```
### Catching All Exceptions
```python
try:
result = risky_operation()
except Exception as e:
print(f"Error: {e}")
```
### Bare except
```python
try:
result = risky_operation()
except:
print("Something went wrong")
```
## The else Clause
```python
try:
value = int(input("Enter a number: "))
result = 10 / value
except ValueError:
print("Invalid input")
except ZeroDivisionError:
print("Can't divide by zero")
else:
print(f"Result: {result}") # Only if no exception
```
## The finally Clause
```python
try:
file = open("file.txt", "r")
content = file.read()
except FileNotFoundError:
print("File not found")
finally:
print("This always runs")
# file.close() # Better to use context manager
```
## Common Exception Types
### Built-in Exceptions
| Exception | When Raised |
|-----------|-------------|
| ZeroDivisionError | Division by zero |
| ValueError | Wrong value for operation |
| TypeError | Wrong type for operation |
| IndexError | Index out of range |
| KeyError | Key not found in dict |
| AttributeError | Attribute not found |
| FileNotFoundError | File doesn't exist |
| ImportError | Module import fails |
### Examples
```python
# IndexError
numbers = [1, 2, 3]
print(numbers[5]) # IndexError: list index out of range
# KeyError
d = {"a": 1}
print(d["b"]) # KeyError: 'b'
# TypeError
print("hello" + 5) # TypeError: can only concatenate str
# ValueError
print(int("abc")) # ValueError: invalid literal
```
## Raising Exceptions
### raise Statement
```python
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
try:
result = divide(10, 0)
except ValueError as e:
print(e) # Cannot divide by zero
```
### Raising with No Argument
```python
try:
raise ValueError("Custom error")
except ValueError:
print("Caught it")
raise # Re-raise the same exception
```
## Custom Exceptions
### Defining Exception Classes
```python
class ValidationError(Exception):
"""Raised when validation fails."""
pass
class PositiveNumberError(ValidationError):
"""Raised when number is not positive."""
def __init__(self, value):
self.value = value
super().__init__(f"Expected positive number, got {value}")
# Using custom exceptions
def process_number(n):
if n < 0:
raise PositiveNumberError(n)
return n * 2
try:
process_number(-5)
except PositiveNumberError as e:
print(e) # Expected positive number, got -5
```
## Exception Hierarchy
```text
BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception
├── StopIteration
├── ArithmeticError
│ ├── FloatingPointError
│ ├── OverflowError
│ └── ZeroDivisionError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── OSError
│ ├── FileNotFoundError
│ └── PermissionError
└── ...
```
## Catching Exception Hierarchy
```python
# Catch all arithmetic errors (includes ZeroDivisionError, OverflowError)
try:
result = 10 / 0
except ArithmeticError:
print("Math error")
# This catches ZeroDivisionError too since it's a subclass
```
## Practical Patterns
### Exception in Functions
```python
def safe_divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
result = safe_divide(10, 0)
if result is None:
print("Division failed")
```
### Retry Pattern
```python
import time
def retry(func, max_attempts=3, delay=1):
for attempt in range(max_attempts):
try:
return func()
except Exception as e:
if attempt == max_attempts - 1:
raise
print(f"Attempt {attempt + 1} failed: {e}")
time.sleep(delay)
```
### With Resources
```python
try:
with open("file.txt", "r") as file:
content = file.read()
except FileNotFoundError:
content = ""
```
## Best Practices
1. **Be specific** - Catch specific exceptions, not bare `except:`
2. **Clean up** - Use `finally` or context managers
3. **Reraise** - Let exceptions propagate when appropriate
4. **Don't suppress** - Don't silently catch exceptions without action
5. **Custom exceptions** - Create specific exceptions for your domain
```python
# Good
try:
result = int(user_input)
except ValueError:
print("Please enter a valid number")
# Bad
try:
result = int(user_input)
except:
pass
```
## Summary
- Syntax errors: caught before runtime
- Exceptions: occur during execution
- Use `try/except` to handle exceptions
- `except ExceptionType` catches specific exceptions
- `except Exception as e` captures the error object
- `else` runs if no exception
- `finally` always runs
- `raise` triggers an exception
- Create custom exceptions by extending `Exception`
- Use context managers (`with`) for resource cleanup
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →