Python Exception Handling — try, except, raise
After completing this topic, you will:
Understand how errors occur in Python, how to catch errors using try/except, how to raise errors directly using raise, and how to perform cleanup operations using finally.
Errors Kill Programs
In Python, when an error occurs, the program immediately stops.
numbers = [1, 2, 3]print(numbers[10]) # IndexError: list index out of rangeprint("This line never runs")All code after the line where the error occurs will not be executed. If it's a web server, the server itself will crash. If it's a data analysis script, it will process 999 out of 1000 items and then fail completely.
Exception handling is "a way to handle errors without the program crashing."
try/except — Catching Errors
try: result = 10 / 0except ZeroDivisionError: print("Cannot divide by zero")
print("Program continues") # This line will be executed!If an error occurs within the try block, the program jumps to the except block. The program will not stop and will continue executing.
Getting the Error Message
try: value = int("hello")except ValueError as e: print(f"Error: {e}") # Error: invalid literal for int() with base 10: 'hello'By using as e, you can receive the error object and find out what went wrong.
Handling Multiple Errors Separately
def safe_divide(a, b): try: result = a / b return round(result, 2) except ZeroDivisionError: print("Denominator cannot be zero") return None except TypeError: print("Both arguments must be numbers") return None
safe_divide(10, 0) # Denominator cannot be zerosafe_divide("10", 2) # Both arguments must be numberssafe_divide(10, 3) # 3.33You can handle different error types in different ways. You can also use except Exception to catch all errors at once, but it's not recommended — it makes debugging difficult because you won't know what error occurred.
Common Error Types
| Error | Cause | Example |
|---|---|---|
ValueError | Invalid value | int("abc") |
TypeError | Incorrect type | "3" + 5 |
IndexError | Index out of range | [1,2,3][10] |
KeyError | Dictionary key does not exist | {"a": 1}["b"] |
FileNotFoundError | File does not exist | open("none.txt") |
ZeroDivisionError | Division by zero | 10 / 0 |
AttributeError | Accessing a non-existent attribute/method | None.upper() |
These are the 7 most common errors you'll encounter in everyday programming.
else and finally
try: f = open("data.txt", "r") content = f.read()except FileNotFoundError: print("File not found")else: # Only executed if the try block succeeds print(f"Read {len(content)} characters")finally: # Always executed, regardless of success or failure print("Cleanup done")| Block | Execution Timing |
|---|---|
try | Always attempted |
except | When an error occurs |
else | When successful without errors |
finally | Always (both success and failure) |
finally is used for cleanup operations such as closing files, terminating network connections, or deleting temporary files. It's code that must always be executed, whether or not an error occurred.
raise — Raising Errors Directly
def withdraw(balance, amount): if amount <= 0: raise ValueError("Withdrawal amount must be positive") if amount > balance: raise ValueError(f"Insufficient funds: balance={balance}, requested={amount}") return balance - amount
try: new_balance = withdraw(1000, 5000)except ValueError as e: print(f"Transaction failed: {e}") # Transaction failed: Insufficient funds: balance=1000, requested=5000raise is a way to declare that "this situation is not normal." It passes the responsibility to the calling side to handle the error using try/except.
Custom Error Classes
class InsufficientFundsError(Exception): def __init__(self, balance, amount): self.balance = balance self.amount = amount super().__init__( f"Cannot withdraw {amount} from balance {balance}" )
def withdraw(balance, amount): if amount > balance: raise InsufficientFundsError(balance, amount) return balance - amount
try: withdraw(1000, 5000)except InsufficientFundsError as e: print(e) # Cannot withdraw 5000 from balance 1000 print(e.balance) # 1000 print(e.amount) # 5000You can inherit from Exception to create your own custom errors. This allows you to include additional information (balance, requested amount) in the error, so that the error-handling code can respond more precisely.
Real-World Pattern — File Processing
def read_config(filepath): try: with open(filepath, "r", encoding="utf-8") as f: lines = f.readlines() except FileNotFoundError: print(f"Config file not found: {filepath}") return {} except PermissionError: print(f"Permission denied: {filepath}") return {} config = {} for i, line in enumerate(lines, 1): line = line.strip() if not line or line.startswith("#"): continue if "=" not in line: print(f"Warning: invalid format at line {i}: {line}") continue key, value = line.split("=", 1) config[key.strip()] = value.strip() return config
# config.txt:# host = localhost# port = 3000# # this is a commentsettings = read_config("config.txt")print(settings) # {'host': 'localhost', 'port': '3000'}This pattern is very common in real-world scenarios:
- File opening failures are handled with
except. - Incorrect lines during parsing only receive a warning and are skipped.
- Empty lines and comments (
#) are ignored.
The program doesn't crash just because it can't open a configuration file; instead, it operates with default values or displays a warning.
Anti-Patterns — Things Not to Do
Ignoring All Errors
# Bad — Swallowing the errortry: risky_operation()except Exception: pass # What happened? Nobody knows
# Good — At least log ittry: risky_operation()except Exception as e: print(f"Warning: {e}") # or logging.warning(...)except: pass ignores all errors. The program won't crash, but you won't know what went wrong. It will be very difficult to debug later.
Overly Broad except
# Bad — Catches a typo (NameError)try: valeu = int(input("Enter number: ")) # typo: valeuexcept Exception: print("Invalid input") # NameError, but "Invalid input"?
# Good — Catch only the expected errorstry: value = int(input("Enter number: "))except ValueError: print("Invalid input — please enter a number")Key Takeaways
| Syntax | Role | When to Use |
|---|---|---|
try | Wrap code that might raise an error | External input, files, network, etc. |
except | Catch specific errors and handle them | Need different handling for each error type |
else | Execute only on success | Additional actions when no error occurs |
finally | Always execute | Cleanup operations (close files, terminate connections) |
raise | Raise errors directly | Invalid input, violation of business rules |
The key principle of exception handling: Catch only the errors you expect, catch them as narrowly as possible, and don't ignore them. Thinking that wrapping everything in try/except will make it safe is a misconception. Incorrect exception handling can hide errors and make debugging even more difficult than when there are no exceptions.