Python Exception Handling: Try, Except, Finally
What is an exception?
An exception is a runtime error that occurs during program execution. When something unexpected happens, Python raises an exception and stops running unless you catch it.
Without exception handling, a single error crashes your entire program. For example, if your code processes a batch of files and one is corrupted, the whole job fails.
Two types of errors in Python
Python distinguishes between syntax errors and exceptions.
Syntax errors happen when your code violates Python’s grammar rules. The Python parser catches them before the program even runs, so you can’t handle them with try-except.
Exceptions occur during execution when something goes wrong. These are runtime errors you can handle programmatically. A ZeroDivisionError, a KeyError, or trying to open a file that doesn’t exist all raise exceptions.
The try-except block
The try block contains code that might raise an exception. If an exception occurs, the except block handles it:
try:
x = 10 / 0
except ZeroDivisionError as e:
print(f"Caught error: {e}")
Output:
Caught error: division by zero
If no exception occurs, the except block is skipped. If an exception occurs but doesn’t match the type listed in except, it propagates to an outer handler or crashes the program.
The as e part captures the exception object so you can access its message or inspect it further.
Handling multiple exceptions
Different exceptions need different responses. Use multiple except blocks:
data = [5, "hello", 0, 10, "world"]
for i in data:
try:
x = 10 / i
except ZeroDivisionError:
print("Cannot divide by zero")
except TypeError:
print(f"Cannot divide by {type(i).__name__}")
Output:
Cannot divide by str
Cannot divide by zero
Cannot divide by str
Each except clause catches a specific exception type. When an exception is raised, Python checks each except in order and runs the first match.
Avoid bare except: (with no exception type) because it catches SystemExit, KeyboardInterrupt, and other low-level exceptions you usually don’t want to silence.
The else block
The else block runs only if no exception occurred in the try block:
try:
x = 10 / 2
except ZeroDivisionError:
print("Division by zero")
else:
print(f"Division successful: {x}")
Output:
Division successful: 5.0
This separates the risky code (in try) from the “success path” logic (in else). It’s cleaner than putting the success code after the try-except-else block.
The finally block
The finally block always runs, whether an exception occurred or not. It’s useful for cleanup: closing files, releasing resources, or logging.
try:
x = 10 / 0
except ZeroDivisionError:
print("Caught zero division")
finally:
print("Cleanup code runs here")
Output:
Caught zero division
Cleanup code runs here
Even if you raise an exception inside the except block, finally still runs. This makes it ideal for resource cleanup.
Raising exceptions manually
Use the raise keyword to trigger an exception when your code detects an invalid state:
def process_numbers(numbers):
for num in numbers:
if num % 2 == 0:
raise ValueError(f"Even number encountered: {num}")
return [n for n in numbers]
try:
result = process_numbers([1, 2, 3, 5])
except ValueError as e:
print(f"Error: {e}")
Output:
Error: Even number encountered: 2
This enforces constraints in your code. If an even number appears, you stop and report the error.
Common built-in exceptions
Python has many built-in exception types:
ZeroDivisionError: Division or modulo by zero.ValueError: An operation receives an argument with the right type but invalid value (e.g.,int("hello")).TypeError: An operation is applied to an object of the wrong type (e.g., adding a string to a number).IndexError: A sequence index is out of range.KeyError: A dictionary key is not found.FileNotFoundError: A file operation fails because the file doesn’t exist (subclass ofOSError).ImportError: An import statement fails or the module doesn’t exist.ModuleNotFoundErroris a more specific subclass.AttributeError: An object doesn’t have a requested attribute.NameError: A variable name is not defined.RecursionError: The recursion depth limit is exceeded.AssertionError: Anassertstatement’s condition is False.
See the official Python documentation for the full exception hierarchy. Understanding exceptions is key to building robust programs, similar to writing and reading files correctly.
Python exception handling best practices
Keep these guidelines in mind when handling exceptions:
- Put only the risky code in the
tryblock. Don’t include unrelated logic that shouldn’t fail. - Catch specific exceptions, not generic
Exceptionor bareexcept:. The more specific your catch, the less you mask bugs. - Use
elseto separate the success path from the try-except block. - Use
finallyfor cleanup, especially with file operations and resource management. - Raise exceptions with clear messages so debugging is easier later.
Exceptions are a natural part of programming. How does your code currently handle errors, and where could exception handling make it more robust?