← Reference · Nestor G Pestelos Jr · Print this page
Programming Languages · Python
Python Exception Handling
Reference entry · last updated September 22, 2026
Python exception handling is the try statement and the raise statement working together. A try statement runs a suite under one or more handlers (except), an optional no-error branch (else), and an optional cleanup branch (finally). The raise statement starts a new exception or re-raises the active one. Exceptions are ordinary objects, and the built-in hierarchy separates ordinary program errors from the system-exiting signals.[1][2][3]
First principles and definitions
Exceptions are objects
An exception is an instance of a class. Raising one creates or selects that instance, and a handler binds it to a name with except SomeError as exc. Because the raised value is an object, it carries attributes, and Python supplies useful ones such as args and, when chained, __cause__ and __context__.[1][3]
The BaseException hierarchy
BaseException is the root of the exception hierarchy. Exception derives from it and is the base for the errors a program normally handles, such as ValueError, KeyError, and OSError. SystemExit, KeyboardInterrupt, and GeneratorExit derive directly from BaseException, not from Exception.[4]
The split is deliberate. Catching Exception leaves the interpreter-control signals alone, while a bare except: is equivalent to except BaseException: and can swallow an interrupt or an exit request. Verified: issubclass(KeyboardInterrupt, Exception) is False, and issubclass(ValueError, Exception) is True.
Propagation and handlers
When an exception occurs in the try suite, Python searches the except clauses in order until one matches, then runs that handler. A clause that names a class also handles any of its subclasses. At most one handler runs. If no handler matches, the exception propagates to the next enclosing try, and an unhandled exception ends the program with a traceback.[1][2]
The try statement
except
A try statement may have more than one except clause, each naming a different exception. The clause runs only if its named class matches the raised exception. Clauses are inspected in order and the first match wins, so a specific type must come before a broader one that would also match it, and an expression-less except: must be last. An except expression may also name a tuple of types, except (TypeError, KeyError):. Catching the specific type keeps the handler meaningful; catching the broad type hides bugs.[1][2]
try:
value = int(raw)
except ValueError as exc:
print("not an integer:", exc)
The as target
A handler that binds the exception with except SomeError as exc clears exc at the end of the clause. The clause behaves as if its body ran inside a try statement whose finally clause deletes the name, so exc is unavailable after the handler. Python clears it because the exception's traceback forms a reference cycle with the stack frame, which would keep that frame's locals alive until the next garbage collection. Assign the exception to a second name inside the handler to keep it after the clause.[2]
Verified on Python 3.13.7: a name bound by as raised NameError when read after the handler.
else
An else clause is executed if control leaves the try suite with no exception, and it is skipped when a return, continue, or break leaves the suite. It must follow all except clauses, and exceptions raised in the else clause are not handled by the preceding except clauses. The practical use is to narrow the guarded region: keep only the risky call in the try, and put the follow-up work in else.[2]
finally
A finally clause is a cleanup handler. It runs as the last task before the try statement completes, whether or not an exception occurred, and whether or not the suite returned. Verified: a return in the try suite still runs the finally block and skips the else block.[2]
Raising exceptions
raise and re-raise
raise with an exception instance or class starts that exception. When a class is given, Python instantiates it with no arguments. A bare raise inside a handler re-raises the active exception, which is how a handler can log and then decline to suppress. A bare raise requires an active exception and raises RuntimeError when there is none. Raising a named object instead, raise exc, adds the current frame to the traceback, so the two forms are not interchangeable. Verified on Python 3.13.7: a bare re-raise kept three traceback frames where raise exc produced four.[3]
try:
handle()
except ValueError:
log()
raise
Exception chaining
When a handler raises a new exception, Python records the original as the implicit context and prints both in the traceback under "During handling of the above exception, another exception occurred". The optional from clause makes the link explicit: raise NewError(...) from exc attaches exc to the __cause__ attribute.[1][3]
try:
parse(text)
except ValueError as exc:
raise ConfigError("bad config") from exc
Custom exceptions
A program can define its own exception classes. The convention is to derive from Exception, directly or indirectly, and to keep the class simple. An exception class carries a name that the caller can catch, which is more precise than returning an error value or raising a built-in type that means something else.[1]
class ConfigError(Exception):
pass
The usual reason to define one is to carry data a handler reads. The tutorial describes exception classes as kept simple and often offering a number of attributes that let a handler extract information about the error. A subclass that overrides __init__ calls super().__init__() with the message so args and str() keep working, then stores the extra fields as attributes.[1][4]
class RateLimited(Exception):
def __init__(self, retry_after):
super().__init__(f"rate limited, retry after {retry_after}s")
self.retry_after = retry_after
EAFP and LBYL
Python documents two opposing styles. EAFP, "easier to ask for forgiveness than permission," assumes a valid key or attribute and catches the exception if the assumption fails; it is characterized by many try and except statements. LBYL, "look before you leap," tests preconditions before the call and is characterized by many if statements.[5]
The glossary notes that the LBYL style can introduce a race condition between the check and the act in a multi-threaded program, because the condition can change in the gap. EAFP performs the operation once and reacts to the result.[5]
# EAFP
try:
value = mapping[key]
except KeyError:
value = default
# LBYL
value = mapping[key] if key in mapping else default
Guidelines
- Catch the specific type you can handle. A handler should do something for the exception it names.
- Avoid a bare
except:. It catchesBaseException, includingKeyboardInterruptandSystemExit. CatchExceptionor a narrower type instead. - Narrow the guarded region. Put the risky call in the
tryand the follow-up inelse, so unrelated failures are not caught. - Use
finallyor a context manager for cleanup. Thewithstatement expresses the common case more directly than a manualtryandfinally. - Preserve the cause when you re-raise. Use
raise ... from excso the original failure survives in the traceback.
See also
References
- ^ Python Documentation, "Errors and Exceptions." Free full text: docs.python.org/3/tutorial/errors.html
- ^ Python Documentation, "The try statement." Free full text: docs.python.org/3/reference/compound_stmts.html
- ^ Python Documentation, "The raise statement." Free full text: docs.python.org/3/reference/simple_stmts.html
- ^ Python Documentation, "Built-in Exceptions." Free full text: docs.python.org/3/library/exceptions.html
- ^ Python Documentation, "Glossary," entries for EAFP and LBYL. Free full text: docs.python.org/3/glossary.html