Python try and except statements let a program respond to expected runtime errors instead of stopping abruptly. They are useful when reading files, converting user input, making network requests, accessing dictionaries, or working with resources that may be unavailable.
Good exception handling does not hide every problem. It catches specific failures, provides useful context, recovers only when recovery makes sense, and allows unexpected programming errors to remain visible. This guide covers try, except, else, finally, multiple exception types, raising exceptions, custom exceptions, logging, and practical patterns.
Related guides include Python data types, functions, and logging.
What Is an Exception?
An exception is an object that represents an error detected while a program is running. For example:
number = int("hello")Python raises ValueError because the text cannot be converted to an integer. Without handling, the program stops and prints a traceback.
The official Python errors and exceptions tutorial explains the language syntax. The built-in exceptions reference describes standard exception classes.
Your First try and except
raw_value = input("Enter an integer: ")
try:
number = int(raw_value)
except ValueError:
print("That is not a valid integer.")Python executes the code inside try. If ValueError occurs, execution moves to the matching except block.
Catch the Exception Object
try:
number = int("twelve")
except ValueError as error:
print(f"Conversion failed: {error}")The exception object contains a message that can help diagnose the failure. Use it carefully in user-facing output because technical messages may reveal internal details.
Catch Specific Exceptions
Avoid a broad except Exception when you know which operation may fail:
from pathlib import Path
path = Path("settings.txt")
try:
content = path.read_text(encoding="utf-8")
except FileNotFoundError:
print("The settings file does not exist.")
except PermissionError:
print("The settings file cannot be read.")
except UnicodeDecodeError:
print("The file is not valid UTF-8 text.")Specific handlers allow different recovery decisions. The pathlib guide covers file paths, while the FileNotFoundError guide explains path-related failures.
Catch Multiple Exceptions Together
When several exception types require the same response, place them in a tuple:
try:
value = int(input("Number: "))
result = 100 / value
except (ValueError, ZeroDivisionError) as error:
print(f"The calculation could not be completed: {error}")Use separate handlers when the user needs different messages or the program needs different recovery logic.
Use else for the Success Path
The else block runs only when no exception occurs:
try:
age = int(input("Age: "))
except ValueError:
print("Enter a whole number.")
else:
print(f"Next year you will be {age + 1}.")Keeping success code in else narrows the protected area and prevents unrelated errors from being caught accidentally.
Use finally for Cleanup
The finally block runs whether an exception occurs or not:
file = None
try:
file = open("data.txt", "r", encoding="utf-8")
content = file.read()
except OSError as error:
print(f"File operation failed: {error}")
finally:
if file is not None:
file.close()For files, a context manager is normally better:
try:
with open("data.txt", "r", encoding="utf-8") as file:
content = file.read()
except OSError as error:
print(f"File operation failed: {error}")finally remains important for resources that need explicit cleanup, such as locks, temporary state, or some external connections.
Keep the try Block Small
This block protects too much code:
try:
number = int(raw_value)
result = calculate_report(number)
save_report(result)
except ValueError:
print("Invalid input")A ValueError from calculate_report() might be mistaken for invalid user input. A clearer structure is:
try:
number = int(raw_value)
except ValueError:
print("Invalid input")
else:
result = calculate_report(number)
save_report(result)Raise an Exception with raise
Your own functions should reject invalid states explicitly:
def calculate_discount(total: float, rate: float) -> float:
if total < 0:
raise ValueError("total cannot be negative")
if not 0 <= rate <= 1:
raise ValueError("rate must be between 0 and 1")
return total * rateRaising a standard exception gives the caller a clear contract and prevents an incorrect result from spreading through the program.
Re-Raise an Exception
Sometimes you need to record context and still allow the failure to continue:
import logging
def load_report(path: str) -> str:
try:
with open(path, "r", encoding="utf-8") as file:
return file.read()
except OSError:
logging.exception("Could not load report from %s", path)
raiseA bare raise inside an exception handler preserves the original traceback.
Chain Exceptions with from
Exception chaining adds a clearer high-level message while preserving the original cause:
def read_port(value: str) -> int:
try:
port = int(value)
except ValueError as error:
raise ValueError("PORT must be a whole number") from error
if not 1 <= port <= 65535:
raise ValueError("PORT must be between 1 and 65535")
return portThe traceback shows both the conversion failure and the application-specific explanation.
Create a Custom Exception
A custom exception is useful when callers need to distinguish a domain rule from ordinary technical errors:
class InsufficientBalanceError(Exception):
"""Raised when an account cannot complete a withdrawal."""
def withdraw(balance: float, amount: float) -> float:
if amount <= 0:
raise ValueError("amount must be positive")
if amount > balance:
raise InsufficientBalanceError("insufficient balance")
return balance - amountInherit from Exception and give the class a meaningful name ending in Error.
Validate User Input in a Loop
def read_age() -> int:
while True:
try:
age = int(input("Age: "))
except ValueError:
print("Enter a whole number.")
continue
if age < 0:
print("Age cannot be negative.")
continue
return ageThis function separates conversion errors from business validation. The loop ends only when valid input is returned.
Handle Dictionary Lookups
Use a default when a missing key is normal:
user = {"name": "Nina"}
role = user.get("role", "guest")Catch KeyError when the key is required and its absence is exceptional:
try:
email = user["email"]
except KeyError:
print("The user record has no email field.")The right approach depends on the data contract, not only on syntax.
Handle Network Requests
import requests
try:
response = requests.get("https://example.com/api/items", timeout=10)
response.raise_for_status()
data = response.json()
except requests.Timeout:
print("The service took too long to respond.")
except requests.ConnectionError:
print("Could not connect to the service.")
except requests.HTTPError as error:
print(f"The server returned an HTTP error: {error}")
except requests.RequestException as error:
print(f"The request failed: {error}")The Requests guide explains status codes, timeouts, sessions, and JSON.
Log Unexpected Failures
import logging
logging.basicConfig(level=logging.INFO)
try:
process_orders()
except OSError:
logging.exception("Order processing failed because of a system error")logging.exception() records the traceback when called inside an exception handler. Avoid logging passwords, tokens, or sensitive personal information.
Do Not Silence Errors
This pattern is dangerous:
try:
process_data()
except Exception:
passIt hides every ordinary exception and gives no evidence that processing failed. At minimum, handle a specific exception and make a deliberate recovery decision.
EAFP and LBYL
Python code often follows “easier to ask forgiveness than permission” (EAFP): attempt an operation and handle the expected exception.
try:
value = settings["theme"]
except KeyError:
value = "light"“Look before you leap” (LBYL) checks first:
if "theme" in settings:
value = settings["theme"]
else:
value = "light"Neither style is universally superior. Use the form that is correct, readable, and safe for the operation.
Test Exception Behavior
import pytest
def test_discount_rejects_invalid_rate():
with pytest.raises(ValueError, match="between 0 and 1"):
calculate_discount(100, 1.5)The Pytest guide covers assertions, fixtures, and exception tests.
Common Mistakes
- Catching
Exceptionaround a large block of code. - Using an empty
exceptblock. - Catching an exception and continuing with invalid data.
- Displaying technical tracebacks directly to end users.
- Using exceptions for ordinary control flow when a simple check is clearer.
- Forgetting cleanup when a resource is acquired manually.
- Raising a generic exception when a standard specific class exists.
- Losing the original cause instead of chaining exceptions.
Practical Example: Safe Configuration Loader
import json
from pathlib import Path
class ConfigurationError(Exception):
"""Raised when application configuration cannot be loaded."""
def load_configuration(path: Path) -> dict:
try:
text = path.read_text(encoding="utf-8")
data = json.loads(text)
except FileNotFoundError as error:
raise ConfigurationError(f"configuration not found: {path}") from error
except PermissionError as error:
raise ConfigurationError(f"configuration cannot be read: {path}") from error
except json.JSONDecodeError as error:
raise ConfigurationError(f"configuration contains invalid JSON: {path}") from error
if "database_url" not in data:
raise ConfigurationError("database_url is required")
return dataThe function translates low-level failures into one application-specific exception while preserving each original cause.
Frequently Asked Questions
Should every function use try and except?
No. Add handlers where the program can provide context, recover, clean up, or translate the error. Let other exceptions propagate to an appropriate boundary.
What is the difference between else and finally?
else runs only after a successful try. finally runs whether the operation succeeds or fails.
Can one except handle several errors?
Yes. Put exception classes in a tuple when they require the same response.
Why should the try block be small?
A small block reduces the chance of catching an exception from the wrong operation.
Should I print or log an exception?
Print may be sufficient for a small command-line exercise. Applications normally benefit from structured logging and a separate user-friendly message.
Conclusion
Python try and except statements make programs more dependable when used deliberately. Protect the operation that may fail, catch specific exceptions, keep the success path in else, and use finally for unavoidable cleanup.
Raise clear exceptions when your own function receives invalid data, preserve causes with chaining, and test failure paths. Good exception handling does not make errors disappear—it makes them understandable and manageable.






