No matter how carefully a programmer designs an algorithm, runtime errors happen: a user enters letters instead of a number, a network connection drops, or a requested file does not exist on disk. Unhandled runtime errors crash programs with ugly tracebacks. Exception Handling allows Python programs to catch errors gracefully and recover without crashing.
Figure 38.1: Python standard exception hierarchy showing BaseException root and catchable Exception subclasses.
try, except, else, finallydef divide_numbers(a, b):
try:
# Code that might raise an exception:
result = a / b
except ZeroDivisionError as err:
# Executes ONLY if ZeroDivisionError occurred:
print(f"Error caught: Cannot divide by zero! ({err})")
return None
except TypeError as err:
# Executes if wrong data type was passed:
print(f"Error caught: Numbers must be numeric! ({err})")
return None
else:
# Executes ONLY if NO exception was raised:
print("Division completed smoothly without errors.")
return result
finally:
# ALWAYS executes no matter what:
print("Cleaning up resources and logging attempt.")
print("Result 1:", divide_numbers(20, 4))
print("---")
print("Result 2:", divide_numbers(20, 0))
In professional systems, you can create domain-specific exceptions by subclassing the built-in Exception class:
class InsufficientBalanceError(Exception):
"""Raised when an ATM withdrawal exceeds available balance."""
def __init__(self, balance, amount):
super().__init__(f"Attempted to withdraw ₹{amount}, but balance is only ₹{balance}!")
self.balance = balance
self.amount = amount
def withdraw(balance, amount):
if amount > balance:
raise InsufficientBalanceError(balance, amount)
return balance - amount
try:
withdraw(1000, 2500)
except InsufficientBalanceError as ex:
print("Transaction Blocked:", ex)
except: catches everything, including system interrupts like KeyboardInterrupt (Ctrl + C) and SystemExit. Always catch specific exceptions (e.g. except (ValueError, KeyError):) or catch except Exception:.