Programs fail: files go missing, users type letters where numbers belong, networks drop. Exception handling is how Python lets you respond to these errors gracefully instead of crashing. Master it and your code becomes robust, predictable, and far easier to debug.
This guide covers the full toolkit end-to-end: try/except, catching specific vs multiple exceptions, the else and finally clauses, raising your own errors with raise, building custom exception classes, exception chaining, and the patterns professionals actually use. Every example is runnable with its output shown.
Errors vs Exceptions
A syntax error stops your code from running at all. An exception happens while the program runs — it is a recoverable runtime event you can catch and handle.
print(10 / 0) # this line runs, then raises an exception
Output:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
The try / except Block
Wrap risky code in try; handle failures in except. The program keeps running instead of crashing.
try:
result = 10 / 0
except ZeroDivisionError:
print("You can't divide by zero!")
print("Program continues...")
Output:
You can't divide by zero!
Program continues...
Catch Specific Exceptions (and the Error Object)
Always catch the specific exception you expect. Use as e to access the error message.
try:
age = int("not a number")
except ValueError as e:
print(f"Invalid input: {e}")
Output:
Invalid input: invalid literal for int() with base 10: 'not a number'
Avoid a bareexcept:orexcept Exception:that swallows everything — it hides bugs and even catchesKeyboardInterrupt. Catch what you can actually handle.
Handling Multiple Exceptions
Use several except clauses, or group related ones in a tuple.
def safe_divide(a, b):
try:
return int(a) / int(b)
except ValueError:
return "Please enter numbers"
except ZeroDivisionError:
return "Cannot divide by zero"
print(safe_divide(10, 2))
print(safe_divide(10, 0))
print(safe_divide("x", 2))
Output:
5.0
Cannot divide by zero
Please enter numbers
Grouping with a tuple when the response is the same:
try:
risky()
except (ValueError, TypeError) as e:
print(f"Bad data: {e}")
The else and finally Clauses
else runs only if no exception occurred. finally runs always — success or failure — making it perfect for cleanup like closing files or connections.
def read_value(data, key):
try:
value = data[key]
except KeyError:
print("Key not found")
return None
else:
print("Lookup succeeded")
return value
finally:
print("Done with lookup")
print(read_value({"a": 1}, "a"))
print("---")
print(read_value({"a": 1}, "z"))
Output:
Lookup succeeded
Done with lookup
1
---
Key not found
Done with lookup
None
Raising Exceptions with raise
Use raise to signal an error yourself when an input or state is invalid — this is better than returning a magic value the caller might ignore.
def set_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
return age
try:
set_age(-5)
except ValueError as e:
print(f"Rejected: {e}")
Output:
Rejected: Age cannot be negative
Custom Exception Classes
For domain-specific errors, define your own exception by subclassing Exception. This lets callers catch exactly your error and makes intent obvious.
class InsufficientFundsError(Exception):
"""Raised when a withdrawal exceeds the balance."""
pass
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(f"Tried {amount}, only {balance} available")
return balance - amount
try:
withdraw(100, 250)
except InsufficientFundsError as e:
print(f"Declined: {e}")
Output:
Declined: Tried 250, only 100 available
Exception Chaining with raise ... from
When you catch one error and raise a more meaningful one, use raise NewError(...) from e to preserve the original cause — invaluable when debugging.
class ConfigError(Exception):
pass
def load_port(config):
try:
return int(config["port"])
except KeyError as e:
raise ConfigError("Missing 'port' in config") from e
try:
load_port({})
except ConfigError as e:
print(f"{e} (caused by {e.__cause__!r})")
Output:
Missing 'port' in config (caused by KeyError('port'))
Real-World Example: A Robust Input Loop
A classic use case: keep asking the user until they give valid input, handling both bad values and Ctrl-C cleanly.
def get_positive_int(prompt):
while True:
try:
value = int(input(prompt))
if value <= 0:
raise ValueError("must be positive")
return value
except ValueError as e:
print(f"Invalid: {e}. Try again.")
except (KeyboardInterrupt, EOFError):
print("\nCancelled.")
return None
# age = get_positive_int("Enter your age: ")
This pattern — validate, raise on bad data, catch, re-prompt — is the foundation of reliable, user-facing programs.
Common Mistakes to Avoid
- Bare
except:— catches everything, hides real bugs. Be specific. - Swallowing errors silently with
except: pass— at least log them. - Putting too much in
try— wrap only the line that can fail, so you don't mask unrelated errors. - Using exceptions for normal flow control — prefer an
ifcheck when failure is expected and cheap to test. - Losing the original error — use
raise ... from ewhen re-raising. - Returning instead of cleaning up — use
finally(or awithblock) to release resources.
Summary Table
| Keyword | Runs when | Typical use |
|---|---|---|
try | Always (the risky code) | Wrap code that may fail |
except | An exception occurs | Handle a specific error |
else | No exception occurred | Code that needs the try to succeed |
finally | Always | Cleanup (close files, connections) |
raise | You call it | Signal an error yourself |
raise ... from | Re-raising | Preserve the original cause |
Conclusion
Good exception handling is about being specific and intentional: catch the errors you expect, raise clear errors for invalid states, clean up with finally, and preserve context when re-raising. Done well, it turns crashes into controlled, debuggable behaviour.
Audit one script today: find the line most likely to fail (a file open, an API call, an int() conversion) and wrap just that line in a precise try/except. Small, specific handlers beat one giant catch-all every time.
π¬ Comments (0)
No comments yet. Be the first to share your thoughts!