Back to Blog
Python

Python Exception Handling: try, except, else, finally

python exception handling: Understand Python's try, except, else, and finally blocks, how to raise and chain exceptions, and how to avoid common error-handling mistakes.

try-exceptexception chainingcustom exceptionserror handlingfinally clauseraise statement
Illustration of a shield catching a falling gear, representing Python exception handling with a try-except block.

Python exception handling centers on the try/except statement, and the behavior of each clause determines whether errors are handled cleanly or silently swallowed. The model is simple on the surface, but the interaction between except, else, and finally produces behavior that surprises developers who only use the basic form.

The try/except Block and How It Executes

When a try block raises an exception, Python immediately stops executing that block and searches for a matching except clause. If a clause matches, its body runs and execution continues after the entire try/except statement. If no clause matches, the exception propagates up the call stack until something handles it or the interpreter prints a traceback and exits.

def read_config(path): try: with open(path) as f: return f.read() except FileNotFoundError: return ""

The except clause runs only when the specified exception type is raised. Code after the raising line inside the try block is skipped, which matters when you rely on cleanup happening after a failure. The with statement handles file closing here, but not every resource has that convenience.

Catching Specific Exception Types Instead of a Bare except

A bare except clause catches every exception, including KeyboardInterrupt and SystemExit. That makes debugging harder because the handler may silently swallow errors it was never meant to handle. It also makes the code's failure behavior unpredictable for callers.

try: result = int(user_input) except ValueError: print("Input must be an integer")

Catching ValueError here is precise: it handles the conversion failure while leaving other exceptions visible. When multiple types can be raised, tuple them in one clause or use multiple except clauses. Order matters because Python checks clauses top to bottom, so put specific types before general ones.

The else Clause: Running Code Only on Success

The else block runs only when the try block completes without raising. It keeps success-path code separate from the error-handling path, which reduces the chance that an exception from the success path gets caught by the wrong handler.

try: data = json.loads(raw) except json.JSONDecodeError: data = {} else: print("Parsed successfully")

Code in the else block is not protected by the preceding except clauses. If it raises, the exception propagates normally, which is usually the desired behavior. This separation is what makes else useful: it is not a second try block, it is a way to keep the success path distinct.

finally: Guaranteed Cleanup

The finally block runs whether the try block succeeds, raises, or is interrupted by a return statement. It is the right place for releasing resources that must be released regardless of outcome.

def acquire_lock(): lock.acquire() try: return read_shared_state() finally: lock.release()

The finally block runs before the exception propagates. If finally itself raises, it replaces the original exception, so keep finally code simple and avoid operations that are likely to fail. Releasing a lock or closing a connection rarely raises, which is why this pattern is safe in practice.

Raising Exceptions and Chaining the Original Cause

The raise statement lets you signal failures explicitly. Raising a new exception inside an except block loses the original traceback unless you chain it with from.

try: process_order(order) except OrderError as exc: raise ProcessingError("Order failed") from exc

The from clause sets the __cause__ attribute and produces a traceback that shows both exceptions, making the root cause visible in logs. When you deliberately re-raise the same exception, use a bare raise instead of raising a new one, because bare raise preserves the original traceback.

Custom Exception Classes

Defining your own exception types makes callers able to catch your library's failures without depending on internal details like KeyError or ValueError.

class ConfigError(Exception): pass class MissingSettingError(ConfigError): pass

A hierarchy lets callers catch the base type for broad handling or a specific type for targeted handling. Keep custom exceptions thin; they rarely need extra methods, though attributes can carry useful context such as the name of the missing setting.

Common Mistakes and Their Consequences

One recurring mistake is catching a broad exception and then failing to re-raise or log it. That hides the failure and makes production diagnosis difficult. Another is placing a return inside the try block and forgetting that finally still runs before the return value is delivered, which can produce surprising side effects.

A subtler issue: catching Exception still catches programming errors like TypeError or AttributeError. If the handler cannot meaningfully recover from those, it is better to let them propagate. The same logic applies to catching exceptions you do not intend to handle; a handler that logs and re-raises is often more honest than one that swallows the error.

Performance and Runtime Cost

Exception handling in Python is cheap when no exception is raised; the try block itself adds little overhead. The cost appears when an exception is actually raised, because building the traceback and unwinding the stack is expensive relative to normal control flow.

That means exceptions should be used for exceptional conditions, not for normal control flow. A loop that raises an exception on every iteration to signal "not found" will be noticeably slower than checking a condition first. Use exceptions for failures, not for routine branching, and reserve the expensive traceback construction for cases where something genuinely went wrong.

python exception handling: Practical Usage and Code Examples | RYUSLOG DEV