except Exception is not except:¶
Level: 201 · for Python programmers
One line: except Exception catches every error a program can recover from and lets KeyboardInterrupt and SystemExit through, because those two sit beside Exception under BaseException rather than below it — so a bare except: swallows Ctrl-C and cancels sys.exit(), the first matching clause wins even when a better one follows, and the name after as is gone when the block ends.
issubclass(ValueError, Exception) # True
issubclass(KeyboardInterrupt, Exception) # False
issubclass(SystemExit, Exception) # False
Everything that can be raised in Python descends from BaseException, and directly under it are four classes. Exception is the root of every error a program might handle: ValueError, OSError, KeyError, your own. The other three are not errors. KeyboardInterrupt is the user asking the program to stop, SystemExit is the program asking to stop, and GeneratorExit is a generator being told to finish. They were put beside Exception rather than under it so that a handler written for errors would not intercept a request to stop, and that is exactly the difference between except Exception: and a bare except:, which means except BaseException: and intercepts all of it.
The rest of the page is what except does with the object once it has matched: the order clauses are tried in, what happens to the as name, and the links one exception keeps to another.
The run¶
Verified output of except_exception_is_not_except_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. THE TREE HAS A ROOT ABOVE Exception
[c.__name__ for c in ValueError.__mro__] ['ValueError', 'Exception', 'BaseException', 'object']
[c.__name__ for c in KeyboardInterrupt.__mro__] ['KeyboardInterrupt', 'BaseException', 'object'] no Exception on the way up
issubclass(KeyboardInterrupt, Exception) False
issubclass(SystemExit, Exception) False sys.exit() raises this one
issubclass(GeneratorExit, Exception) False and a closed generator gets this one
2. except Exception LETS Ctrl-C THROUGH; A BARE except DOES NOT
try: escaped: KeyboardInterrupt
raise KeyboardInterrupt
except Exception:
outcome = 'caught'
try:
raise KeyboardInterrupt
except:
outcome = 'a bare except caught it'
outcome 'a bare except caught it'
try:
raise KeyboardInterrupt
except BaseException:
outcome = 'so does BaseException, by name'
outcome 'so does BaseException, by name'
3. sys.exit() IS AN EXCEPTION, AND A BARE except CANCELS THE EXIT
try:
sys.exit(3)
except SystemExit as e:
code = e.code
code 3 sys.exit() raised; nothing exited
try:
sys.exit(3)
except:
swallowed = True
swallowed True the program that meant to stop is still running
4. THE FIRST MATCHING CLAUSE WINS
try:
int('x')
except Exception:
handler = 'Exception'
except ValueError:
handler = 'ValueError'
handler 'Exception' the specific clause below it can never run
issubclass(ValueError, Exception) True because this is true
5. THE as-NAME IS DELETED WHEN THE BLOCK ENDS
try:
1 / 0
except ZeroDivisionError as err:
kind = type(err).__name__
kind 'ZeroDivisionError'
err NameError unbound: the block deleted it on the way out
try:
1 / 0
except ZeroDivisionError as err:
saved = err
type(saved).__name__ 'ZeroDivisionError' keep it by binding another name
6. AN EXCEPTION IS AN OBJECT, AND raise from IS A LINK
try:
try:
{}['k']
except KeyError as e:
raise ValueError('bad config') from e
except ValueError as v:
chain = (type(v).__name__, type(v.__cause__).__name__)
chain ('ValueError', 'KeyError') from e: the cause is on the new exception
try:
try:
{}['k']
except KeyError:
int('x')
except ValueError as v:
ctx = (type(v.__context__).__name__, v.__cause__)
ctx ('KeyError', None) raised while handling: linked as __context__, not __cause__
try:
try:
{}['k']
except KeyError:
raise ValueError('bad config') from None
except ValueError as v:
hidden = (v.__context__ is not None, v.__suppress_context__)
hidden (True, True) from None keeps the context and hides it from the traceback
7. else RUNS WHEN NOTHING WAS RAISED; finally RUNS REGARDLESS
def probe(text):
log = []
try:
log.append(int(text))
except ValueError:
log.append('except')
else:
log.append('else')
finally:
log.append('finally')
return log
probe('1') [1, 'else', 'finally']
probe('x') ['except', 'finally']
8. A GROUP IS CAUGHT IN PIECES, AND NOT BY A PLAIN except
try:
raise ExceptionGroup('two', [ValueError('a'), TypeError('b')])
except* ValueError as eg:
got_v = [type(e).__name__ for e in eg.exceptions]
except* TypeError as eg:
got_t = [type(e).__name__ for e in eg.exceptions]
got_v, got_t (['ValueError'], ['TypeError']) each except* clause got its slice of the group
try: escaped: ExceptionGroup
raise ExceptionGroup('one', [ValueError('a')])
except ValueError:
pass
issubclass(ExceptionGroup, ValueError) False a group is not any of its members
What the run shows¶
Section 1: the tree has a root above Exception. ValueError's ancestry goes through Exception; KeyboardInterrupt's does not. SystemExit, which sys.exit() raises, and GeneratorExit, which a generator receives when it is closed, are the other two that skip it.
Section 2: except Exception lets Ctrl-C through, and a bare except does not. The first block raised KeyboardInterrupt past except Exception:, and the harness that runs this page reports it as escaped. The bare except: in the second block caught it, and except BaseException: in the third did the same by name, which is the honest spelling when catching everything is what you mean, to log and re-raise, say.
Section 3: sys.exit() is an exception, and a bare except cancels the exit. sys.exit(3) raised SystemExit with a code of 3, and nothing exited. The second block is the bug this page exists for: a bare except: caught the exit, and the program that meant to stop is still running. A worker loop written as while True: try: … except: continue cannot be stopped by Ctrl-C or by sys.exit(), only by kill -9.
Section 4: the first matching clause wins. int('x') raises ValueError, and except Exception: comes first and matches, so handler is 'Exception' and the except ValueError: below it can never run. Nothing warns. Specific clauses go first, and issubclass(ValueError, Exception) being True is the reason.
Section 5: the as-name is deleted when the block ends. Inside the block err is the exception, and kind was read from it. After the block err raises NameError: Python deletes the name on the way out, because the exception holds its traceback, the traceback holds the frame, and the frame holds err, which would be a reference cycle keeping every local alive. Bind another name inside the block, as saved = err does, to keep it.
Section 6: an exception is an object, and raise from is a link. raise ValueError(...) from e stores e on the new exception as __cause__, and the pair reads ('ValueError', 'KeyError'). An exception raised while handling another gets the older one as __context__ instead, with __cause__ left None. raise … from None keeps the context on the object and sets __suppress_context__, so the traceback stops showing it while a debugger can still find it. Since 3.11 e.add_note(text) attaches a line to the object that the traceback prints under it.
Section 7: else runs when nothing was raised; finally runs regardless. probe('1') logs the value, then 'else', then 'finally'; probe('x') logs 'except', then 'finally'. else is for the code that should run only on success and should not itself be inside the try. What a return inside finally does to the exception is finally can overwrite the return.
Section 8: a group is caught in pieces, and not by a plain except. An ExceptionGroup holds several exceptions at once, and except* gives each clause the slice that matches it: got_v is ['ValueError'] and got_t is ['TypeError']. A plain except ValueError: does not catch a group that contains a ValueError, because issubclass(ExceptionGroup, ValueError) is False, and the harness reports the group escaping. Groups come from code that runs several things at once, asyncio.TaskGroup above all, which the concurrency library ↗ owns.
If you are coming from Rust¶
Rust has no exceptions and so no filter to get wrong. A recoverable failure is a Result you must look at, and the compiler warns when you do not; an unrecoverable one is a panic!, and Ctrl-C is a signal that ends the process with no unwinding at all, as Ctrl-C is a signal measures from the Python side. The nearest thing to a bare except: is let _ = fallible();, and it has to be written on purpose. The Rust library's Keep going or stop ↗ is the decision this page's hierarchy encodes, and What a panic costs ↗ is the unrecoverable half.
If you are coming from C¶
C reports failure with a return value and errno, and there is no hierarchy to filter on: every check is written by hand, and a check you did not write is a failure you did not see. Ctrl-C is SIGINT, delivered to a handler or killing the process, which the Linux library's signals chapter ↗ owns. Python's KeyboardInterrupt is that signal turned into an exception by a default handler, and section 2 is what happens to it afterwards.
If you are coming from ABAP¶
ABAP's class-based exceptions are a tree too, rooted at CX_ROOT, with CX_STATIC_CHECK, CX_DYNAMIC_CHECK and CX_NO_CHECK beneath it, and CATCH cx_root catches all of them the way except Exception does here. What ABAP does not have is anything beside the root: there is no exception class for the user interrupting the program, so the split in section 2 has no counterpart, and a runtime error that is not an exception object cannot be caught at all. RAISE EXCEPTION … EXPORTING previous = … is section 6's raise from, and the object's PREVIOUS attribute is __cause__. CLEANUP is not finally: it runs only when the exception is going to be handled outside the TRY, not on the normal path, so it has no else either. (Not machine-checked — CI cannot run ABAP.)
Try it¶
- Search a codebase you maintain for a bare
except::grep -rn 'except:' --include='*.py'. For each hit, ask whether Ctrl-C should be able to stop the loop it sits in. Section 3 says it cannot. - Find a worker loop with
except: continueorexcept Exception: passin it, run it, and press Ctrl-C. One of the two spellings stops; the other does not. - Look for an
except Exception:clause that has a more specific clause after it. Section 4 says the second one is dead code, and no tool will have told you. - Take one place where you re-raise a different exception, add
from e, and compare the traceback with and without; then tryfrom Noneand see what disappears. - Run a linter over your code and look for the name after
asbeing used outside its block. Section 5 says that read raisesNameError, and the linter reports it as an undefined name.
See also¶
- Ctrl-C is a signal: where
KeyboardInterruptcomes from, and the one function that turns the signal into it finallycan overwrite the return, A warning is not an exception and The traceback is on the object: the rest of this chapter- Exception hierarchy ↗ in the library reference, the
trystatement ↗, PEP 3134 ↗ for chaining and PEP 654 ↗ for exception groups - Keep going or stop ↗ and What a panic costs ↗ in the Rust library, and Signals ↗ in the Linux library