☰ Learn Python Tutorial Menu

Errors and Exceptions

Written by CSA mentors · Updated 26 Sept 2026 · 5 min read


What happens to your nightly report when row 812,455 has the word "PENDING" in the amount column? If the answer is "the script crashes at 2 am and nobody finds out until 9", you have a problem that this lesson solves. Real data is messy. A blank amount, an API that times out, a missing file, a user typing "abc" where a number belongs. Python's exception system lets you notice a problem, respond sensibly, and either carry on or stop loudly on purpose. The mechanics are easy. The judgement about when to catch and when not to is the real skill.

What an exception is

When Python can't continue, it raises an exception, an object describing what went wrong. If nothing handles it, the program stops and prints a traceback.

Traceback (most recent call last):
  File "report.py", line 12, in <module>
    amount = int(row["amount"])
ValueError: invalid literal for int() with base 10: 'N/A'

Read the last line first, which gives the exception type (ValueError) and message. The line above it says where. These are the ones you'll meet most.

ExceptionTypical cause
ValueErrorRight type, bad value: int("abc")
TypeErrorWrong type: "Rs" + 500
KeyErrorDict key missing
IndexErrorList index out of range
ZeroDivisionErrorDividing by zero, e.g. average of an empty list
FileNotFoundErrorPath is wrong or file absent
AttributeErrorCalling a method that does not exist, often on None

try / except

Flowchart of try, except, else and finally showing which block runs when an error is or is not raised
raw_values = ["2500", "N/A", "1800", "", "4100"]

total = 0
skipped = 0
for text in raw_values:
    try:
        total += int(text)
    except ValueError:
        skipped += 1

print(f"Total {total}, skipped {skipped}")     # Total 8400, skipped 2

Code in the try block runs normally. If it raises the named exception, Python jumps to the except block instead of crashing, then carries on after the whole statement. Catch the specific exception you expect. A bare except: also swallows typos, Ctrl+C and the bugs you actually want to hear about. We reject homework that uses it.

Using the exception object

try:
    rate = float("17%")
except ValueError as err:
    print("Could not parse rate:", err)

Several exception types

def safe_average(values):
    try:
        return sum(values) / len(values)
    except ZeroDivisionError:
        return 0.0
    except TypeError:
        print("Non-numeric value found")
        return None

print(safe_average([10, 20, 30]))     # 20.0
print(safe_average([]))               # 0.0
print(safe_average([10, "x"]))        # message, then None

You can also group types with except (ValueError, TypeError):.

else and finally

def read_config(path):
    try:
        f = open(path)
    except FileNotFoundError:
        print("No config, using defaults")
        return {}
    else:
        data = f.read()          # runs only if open() succeeded
        f.close()
        return {"raw": data}
    finally:
        print("read_config finished")   # runs no matter what

else holds the code that should run only when nothing went wrong, which keeps the try block small. finally always runs and is for cleanup such as closing files or database connections (though with, coming in Lesson 15, usually does that for you).

Raising your own

Sometimes the right response to bad data is to stop, loudly. raise creates an exception on purpose.

def apply_discount(price, percent):
    if not 0 <= percent <= 100:
        raise ValueError(f"percent must be 0-100, got {percent}")
    return price * (1 - percent / 100)

print(apply_discount(1000, 15))     # 850.0
print(apply_discount(1000, 150))    # ValueError with a clear message

Failing early with a precise message beats quietly producing a negative price that surfaces three reports later.

Custom exception classes

class InsufficientFunds(Exception):
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFunds(f"balance {balance}, requested {amount}")
    return balance - amount

try:
    withdraw(500, 800)
except InsufficientFunds as e:
    print("Declined:", e)

When to catch, and when not to

SituationDo
One bad row in a file of thousandsCatch, count it, log it, continue
A network call that may time outCatch, retry a few times, then give up
A bug in your own logicDo not catch; let it crash so you see it
Config file missingCatch and use defaults, or raise with a helpful message
Impossible input to your functionraise ValueError with a clear message

Python people say "ask forgiveness, not permission". try: x = d[key] except KeyError: is considered more Pythonic than checking if key in d first, at least when the missing case is rare.

The 2 am crash at a Karachi bank: The nightly reconciliation died because one row out of well over a million had "PENDING" where an amount should be, and nobody saw the traceback until the morning shift. The rewrite wraps the per-row conversion in try/except ValueError, appends the bad row to an errors list, and finishes the run. Then it raises its own ReconciliationError if more than a tiny fraction of rows failed. A genuine data problem still stops the pipeline. One stray value doesn't.

Keep these five

  • Exceptions describe what went wrong. Read the last traceback line first.
  • try/except SpecificError handles expected problems. Never use a bare except:.
  • else runs on success, finally always runs.
  • raise ValueError("clear message") stops bad input early.
  • Catch what you can handle sensibly. Let real bugs crash.

Homework

  1. Write to_float(text) that returns a float for inputs like "2,500.50" and "Rs 800", and returns None for anything unparseable, without ever crashing.
  2. Given a dict of exchange rates, write a function that converts an amount to PKR and raises a KeyError with a helpful message listing the supported currencies when the currency is unknown.
  3. Create a NegativeStockError class and use it in a sell(stock, qty) function; catch it in a loop over several sales and print which ones were rejected.

Lesson 13 of 26

Sign in to track your progress and earn learning points for every lesson you finish.

Example

print("Hello from CSA!")

for i in range(5):
    print(i * i)
Try it Yourself »