Python Exceptions#
Core Hierarchy#
The relevant hierarchy is:
BaseException
├── KeyboardInterrupt
├── SystemExit
├── GeneratorExit
└── Exception
├── ValueError
├── TypeError
├── KeyError
├── RuntimeError
└── ...
BaseException is the root of Python’s exception hierarchy.
Exception is the base class for ordinary application errors.
Why Application Code Catches Exception#
Normal application code should catch a specific subclass of Exception:
try:
quantity = int(raw_quantity)
except ValueError as exc:
raise ValueError("quantity must be an integer") from exc
When several expected failures share the same recovery behaviour:
try:
quantity = int(raw_quantity)
except (TypeError, ValueError) as exc:
raise ValueError("invalid quantity") from exc
A broad Exception handler may be appropriate at a controlled application
boundary:
try:
run_trade_job()
except Exception:
logger.exception("Trade job failed")
return 1
This catches ordinary application failures while allowing shutdown and user interruption to propagate.
Why Not Catch BaseException?#
KeyboardInterrupt and SystemExit inherit directly from
BaseException, not from Exception.
Therefore:
try:
run_service()
except BaseException:
logger.exception("Failure")
also catches:
Ctrl+CthroughKeyboardInterrupt;sys.exit()throughSystemExit;generator shutdown through
GeneratorExit.
Suppressing these exceptions can prevent a process from terminating normally.
A bare handler has similarly broad behaviour:
try:
run_service()
except:
...
Avoid both forms in normal application code.
Cleanup#
Do not catch BaseException merely to release a resource.
Use finally:
connection = open_connection()
try:
process_trades(connection)
finally:
connection.close()
Or preferably use a context manager:
with open_connection() as connection:
process_trades(connection)
The cleanup runs while the original exception continues to propagate.
Rare BaseException Use#
Low-level infrastructure may occasionally catch BaseException to perform
mandatory cleanup.
It should immediately re-raise:
try:
run_worker()
except BaseException:
release_native_resources()
raise
Suppressing it would interfere with shutdown and interruption.
Exception Chaining#
Translate a low-level exception into a domain exception while preserving the cause:
class TradeNotFoundError(Exception):
pass
def get_trade(trade_id: str) -> Trade:
try:
return trades[trade_id]
except KeyError as exc:
raise TradeNotFoundError(
f"Unknown trade_id: {trade_id}"
) from exc
This gives callers a stable domain-level exception while preserving the original traceback.
Custom Exceptions#
Normal application exceptions should inherit from Exception:
class TradeError(Exception):
pass
class DuplicateTradeError(TradeError):
pass
Do not inherit ordinary application errors directly from BaseException.
Interview Answer#
A concise interview answer is:
BaseExceptionis the root of all Python exceptions.Exceptionis the base class for ordinary application failures. Application code should normally catch a specificExceptionsubclass, or occasionallyExceptionat a system boundary. It should not normally catchBaseExceptionbecause that would also interceptKeyboardInterrupt,SystemExit, andGeneratorExit. Usefinallyor context managers for cleanup, and useraise ... from excwhen translating exceptions.