Topic 11 / 15

Exceptions & Error Handling

~9 min read  //  Python Series  //  Coding India

Errors Are Objects, Not Status Codes

When something goes wrong, Python raises an exception that travels up the call stack until something catches it — or the program exits with a traceback. Learn to read tracebacks bottom-up: the last line is the error, the lines above are the path that led there.

try / except

try:
    age = int(input("Age: "))
except ValueError:
    print("That's not a number.")

Catch specific exceptions. A bare except: swallows everything — including typos in your own code and Ctrl-C — and turns bugs into silent mysteries:

try:
    result = data["score"] / count
except KeyError:
    print("no score field")
except ZeroDivisionError:
    print("count is zero")
except (TypeError, ValueError) as e:    # catch several, keep the object
    print(f"bad data: {e}")

else and finally

try:
    f = open("data.json")
except FileNotFoundError:
    print("missing file")
else:
    # runs only if NO exception was raised
    data = json.load(f)
finally:
    # runs ALWAYS — cleanup belongs here
    print("done")

Raising Exceptions

def set_price(amount):
    if amount < 0:
        raise ValueError(f"price cannot be negative, got {amount}")
    ...

Re-raise after logging with a bare raise, and chain causes with raise ... from e so the original traceback survives:

try:
    charge(card)
except GatewayTimeout as e:
    raise PaymentFailed("payment gateway unreachable") from e

Custom Exceptions

Define your own for your domain — callers can then catch exactly what they care about:

class PaymentError(Exception):
    """Base class for payment failures."""

class CardDeclined(PaymentError):
    pass

class InsufficientFunds(PaymentError):
    def __init__(self, needed, available):
        super().__init__(f"need ₹{needed}, have ₹{available}")
        self.needed = needed
        self.available = available

EAFP — the Python Philosophy

“Easier to Ask Forgiveness than Permission” — try the operation and handle failure, instead of pre-checking every condition:

# LBYL (look before you leap) — racy and verbose
if "score" in data and isinstance(data["score"], int):
    value = data["score"]

# EAFP — pythonic
try:
    value = int(data["score"])
except (KeyError, ValueError):
    value = 0