What's the difference between `except Exception`, a bare `except:`, and catching specific exceptions?
Quick Answer
Catching a **specific exception** (`except ValueError:`) only handles that error type, letting anything unexpected propagate — the safest and most intentional option. `except Exception:` catches almost everything you'd reasonably want to handle, but not `SystemExit`/`KeyboardInterrupt`/`GeneratorExit`. A **bare `except:`** catches literally everything, including those — it's almost always a bug, since it can swallow Ctrl+C, hide `sys.exit()`, and mask programming errors like `NameError` or `AttributeError` typos.
Detailed Answer
Specific: the default, correct choice
try:
value = int(user_input)
except ValueError:
print("please enter a valid number")
Only ValueError (or its subclasses) is handled here — a KeyboardInterrupt
during the try block, or a typo causing a NameError, propagates
normally instead of being silently absorbed. This is almost always what
you want: handle the error you anticipated, let anything else surface.
except Exception: — a broad but bounded net
try:
risky_operation()
except Exception as e:
log.error(f"operation failed: {e}")
Reasonable at a system boundary (a web request handler, a task queue
worker) where you genuinely want to catch "anything that went wrong
during business logic" and convert it to a controlled response/log
entry, without swallowing process-control signals. Still risky if
overused deep inside application logic, since it can mask bugs (a typo
raising NameError gets logged and ignored instead of crashing loudly
during development).
Bare except: — almost always a mistake
try:
risky_operation()
except: # DON'T -- catches EVERYTHING, including:
pass # SystemExit, KeyboardInterrupt, and typos like NameError
A bare except: is equivalent to except BaseException: — it will
swallow sys.exit() (so your program refuses to actually exit),
KeyboardInterrupt (so Ctrl+C appears to do nothing), and any programming
error (AttributeError from a typo) that you'd want to see and fix
immediately. Linters (flake8, ruff) flag bare except: for exactly
this reason.
Multiple specific exceptions
try:
process(data)
except (ValueError, TypeError) as e:
print(f"bad input: {e}")
except KeyError as e:
print(f"missing field: {e}")
A tuple of exception types in one except clause handles them
identically; separate except clauses let you respond differently per
error type. Order matters — Python checks clauses top to bottom and uses
the first match, so a more specific exception type must come before a
more general one that would otherwise shadow it (e.g., except ValueError before except Exception).
The practical rule
Catch the most specific exception type that you know how to handle,
as close as possible to where you can meaningfully react to it. Reserve
except Exception for true boundary layers (top-level request handlers,
background job runners), and never use a bare except: — if you truly
need to catch everything including SystemExit/KeyboardInterrupt
(rare — e.g., a supervisor process), spell out except BaseException:
explicitly so the intent is unambiguous.
Interview-ready summary: Prefer catching the specific exception type
you can actually handle. except Exception is acceptable at application
boundaries as a last line of defense, but never use a bare except: —
it silently swallows SystemExit/KeyboardInterrupt and masks genuine
programming errors that should crash loudly instead.