Back to Blog
Python

Catch All Exceptions in Python: Syntax and Pitfalls

python catch all exceptions: Learn how to catch all exceptions in Python with try/except, understand BaseException vs Exception, and avoid hiding real failures in prod...

exception handlingtry-exceptBaseExceptionexception groupsloggingPython
Illustration of a Python try/except block catching an exception with a safety net, showing the distinction between BaseException and Exception classes.

python catch all exceptions requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When a Python program fails, the interpreter raises an exception. The try/except block is the standard way to intercept that failure and decide what happens next. To catch all exceptions in Python, the common pattern is:

try: result = fetch_data() except Exception as exc: log_error(exc) result = fallback_value()

This catches any exception that inherits from Exception, which covers the vast majority of runtime errors in Python programs. But "all exceptions" is a stronger claim than this code delivers. Understanding the difference matters when you write a handler that is supposed to be a safety net.

The Core Syntax for Catching All Exceptions

A try block can have multiple except clauses. Python evaluates them in order and uses the first one whose exception type matches the raised exception. A clause that names Exception matches that class and every subclass, so it effectively catches all ordinary programming errors.

try: parse_config(file_path) except FileNotFoundError: print("Config file is missing") except PermissionError: print("Config file is not readable") except Exception as exc: print(f"Unexpected error: {exc}")

The final except Exception clause is the catch-all for this block. It handles anything that the more specific clauses did not match. Without it, an unexpected exception would propagate out of the function and potentially terminate the program.

The as exc part binds the exception instance to a name so you can inspect its message, attributes, or traceback. If you only need to react to the fact that something failed, you can omit the as clause entirely.

Why except Exception Does Not Catch Everything

Exception is the base class for most built-in exceptions, but it is not the root of the class hierarchy. The root is BaseException. A small number of exceptions inherit directly from BaseException and bypass except Exception:

  • KeyboardInterrupt, raised when the user presses Ctrl+C
  • SystemExit, raised by sys.exit()
  • GeneratorExit, raised when a generator is closed

These are not ordinary runtime errors. They represent control flow that should normally terminate the program or unwind a generator. Catching them accidentally can make a program impossible to interrupt or exit cleanly.

try: long_running_job() except Exception: pass

If a user presses Ctrl+C during long_running_job(), the KeyboardInterrupt is not caught here. It propagates and the program exits. That is usually the desired behavior.

Catching BaseException When You Truly Need Everything

If your code must react to every exception, including KeyboardInterrupt and SystemExit, you can catch BaseException directly.

try: run_worker() except BaseException as exc: cleanup_resources() raise

This is the only way to guarantee that no exception escapes the block. The bare form except: is equivalent to except BaseException: and has the same effect.

Catching BaseException is rarely the right choice. It interferes with the interpreter's normal shutdown and user-initiated interruption. The main legitimate use is cleanup code that must run no matter how the block ends, and even then finally is often a better tool because it does not suppress the exception.

try: run_worker() finally: cleanup_resources()

The finally block runs whether the try block completes normally, raises an exception, or is interrupted. It does not swallow the exception, so the program still terminates or propagates the failure as intended.

Accessing the Exception Instance and Traceback

The exception instance bound by as carries more than just the message string. It has a __traceback__ attribute that references the traceback object, and you can format the full traceback with the traceback module.

import traceback try: process_batch(items) except Exception as exc: traceback.print_exc() raise

traceback.print_exc() writes the current exception and its traceback to standard error. This is useful in a command-line tool or a script where the default traceback output is acceptable. For a service that writes structured logs, you typically want the exception details as fields rather than as formatted text.

import logging logger = logging.getLogger(__name__) try: process_batch(items) except Exception: logger.exception("Batch processing failed") raise

logger.exception() logs at the ERROR level and automatically includes the current exception traceback. It must be called inside an except block, because it relies on sys.exc_info() to find the active exception.

Re-raising and Chaining Exceptions

A handler that catches an exception and then lets it continue is said to re-raise. The bare raise statement re-raises the currently handled exception without modifying its traceback.

try: connect_to_service() except Exception as exc: log_error(exc) raise

This preserves the original exception and its traceback, so the caller sees the real failure. If you instead raise a new exception, the original one becomes the context for the new one, and you can control that relationship with from.

try: parse_payload(data) except ValueError as exc: raise InvalidPayloadError("Payload did not match the expected schema") from exc

The from exc clause sets the __cause__ attribute on the new exception. When the traceback is printed, Python shows the chain: the new exception, followed by "The above exception was the direct cause of the following exception," and then the original error. This preserves diagnostic information while presenting a more meaningful error type to the caller.

Where Broad Exception Handling Belongs

A catch-all handler is appropriate at a boundary where you control what happens after a failure. Common examples:

  • A request handler in a web framework that converts an unhandled exception into a 500 response
  • A background job runner that records the failure and moves on to the next job
  • A top-level entry point that logs the error and exits with a nonzero status

In all of these, the handler is the last line of defense. It should log enough detail to diagnose the failure and then either re-raise, return an error response, or exit with a failure code. It should not silently continue as if nothing happened.

Inside business logic, broad exception handling hides bugs. If a function catches Exception and returns a default value, a programming error such as a KeyError from a misspelled dictionary key is indistinguishable from a legitimate fallback condition. The error is recorded nowhere, and the program continues with a value that may be wrong.

Operational Concerns: Logging and Observability

The most common production problem with catch-all handlers is that they suppress information. A handler that catches an exception and does nothing makes the failure invisible. The program keeps running, but the state is corrupted or the result is incomplete, and nobody knows why.

When you do catch broadly, log the exception with its traceback. The logger.exception() pattern shown earlier is the minimum. For a service that aggregates logs, make sure the exception message, exception type, and traceback are all present in the log entry. Structured logging libraries can attach these as fields, which makes querying for a specific failure much easier than searching formatted text.

Another operational concern is the cost of formatting a traceback. Formatting is relatively expensive, so avoid calling traceback.format_exc() unless you actually need the string. If the exception is going to be re-raised anyway, let the outer handler format it. If you only need the message for a log line, use str(exc).

There is also a maintainability tradeoff. A catch-all handler that re-raises is transparent: the caller still sees the failure. A catch-all handler that swallows the exception changes the contract of the function. Callers can no longer rely on exceptions to signal failure, so they must check return values or sentinel results instead. That is a design decision, not a default. Reserve swallowing for cases where the fallback behavior is genuinely correct, and document why.

Compatibility Note: Exception Groups

Python 3.11 introduced exception groups and the except* syntax. An exception group is a single exception that carries multiple sub-exceptions. A normal except Exception clause does not match an exception group that contains an Exception subclass, because the group itself is a BaseExceptionGroup. To handle the individual exceptions inside a group, you use except*.

try: run_tasks() except* ValueError: handle_value_errors() except* TypeError: handle_type_errors()

If you need a catch-all for exception groups, except* Exception matches any exception group that contains at least one Exception subclass. This behavior is specific to Python 3.11 and later, so it matters only if your codebase targets that version or newer.

python catch all exceptions: Practical Usage and Code Exampl | RYUSLOG DEV