Bytebook Compiler

Real Programs

Exceptions

Handle errors gracefully with try, except, else and finally, read tracebacks, raise your own errors, and define custom exception classes.

4 min read7 runnable examples4-question quiz

When something goes wrong, Python raises an exception. If nothing handles it, the program stops and prints a traceback — read it from the bottom up: the last line names the error.

Python
marks = [80, 90]
print(marks[5])
Output
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    print(marks[5])
          ~~~~~^^^
IndexError: list index out of range

try / except

Python
def ask_and_divide():
    try:
        n = int(input("Divide 100 by: "))
        print("Result:", 100 / n)
    except ValueError:
        print("That was not a whole number.")
    except ZeroDivisionError:
        print("You cannot divide by zero.")

for _ in range(3):
    ask_and_divide()
Output
Divide 100 by: ten
That was not a whole number.
Divide 100 by: 0
You cannot divide by zero.
Divide 100 by: 4
Result: 25.0
Sample input is includedTry it Yourself

else and finally

  • else runs only if the try block raised no exception.
  • finally runs always — perfect for cleanup.
Python
def read_number(text):
    try:
        value = float(text)
    except ValueError as e:              # 'as e' gives the exception object
        print(f"  error: {e}")
        return None
    else:
        print("  parsed fine")
        return value
    finally:
        print("  (finished trying", repr(text) + ")")

print(read_number("3.5"))
print(read_number("abc"))
Output
  parsed fine
  (finished trying '3.5')
3.5
  error: could not convert string to float: 'abc'
  (finished trying 'abc')
None

Common built-in exceptions

Exception Raised when
ValueError right type, wrong value: int("abc")
TypeError wrong type: "5" + 5
ZeroDivisionError dividing by zero
IndexError list index out of range
KeyError missing dictionary key
AttributeError missing attribute: None.upper()
NameError using an undefined name
FileNotFoundError opening a file that does not exist
RecursionError recursion too deep
Python
tests = [lambda: int("x"), lambda: "5" + 5, lambda: [1][3], lambda: {}["k"], lambda: undefined_name]
for t in tests:
    try:
        t()
    except Exception as e:                      # catch any normal exception
        print(f"{type(e).__name__:15} {e}")
Output
ValueError      invalid literal for int() with base 10: 'x'
TypeError       can only concatenate str (not "int") to str
IndexError      list index out of range
KeyError        'k'
NameError       name 'undefined_name' is not defined

raise

Raise an exception yourself when input is invalid:

Python
def set_age(age):
    if not isinstance(age, int):
        raise TypeError("age must be an integer")
    if age < 0 or age > 150:
        raise ValueError(f"impossible age: {age}")
    return age

for value in (21, -4, "twenty"):
    try:
        print("ok:", set_age(value))
    except (TypeError, ValueError) as e:
        print("rejected:", e)
Output
ok: 21
rejected: impossible age: -4
rejected: age must be an integer

Custom exceptions

Python
class InsufficientBalance(Exception):
    def __init__(self, needed):
        super().__init__(f"need Rs {needed} more")
        self.needed = needed

class Wallet:
    def __init__(self, balance):
        self.balance = balance
    def pay(self, amount):
        if amount > self.balance:
            raise InsufficientBalance(amount - self.balance)
        self.balance -= amount

w = Wallet(500)
for amount in (200, 450):
    try:
        w.pay(amount)
        print(f"paid {amount}, balance {w.balance}")
    except InsufficientBalance as e:
        print("payment failed:", e, "| short by", e.needed)
Output
paid 200, balance 300
payment failed: need Rs 150 more | short by 150

EAFP vs LBYL

Python style often prefers EAFP — "Easier to Ask Forgiveness than Permission" — try the operation and handle failure, rather than LBYL ("Look Before You Leap") with many if checks:

Python
config = {"theme": "dark"}
# LBYL
size = config["size"] if "size" in config else 12
# EAFP
try:
    size = config["size"]
except KeyError:
    size = 12
print(size, config.get("size", 12))      # get() is the simplest here
Output
12 12

Test yourself

4 questions · pick an answer to check it instantly

  1. 1

    When does the else block of a try statement run?

  2. 2

    Which exception does int("12a") raise?

  3. 3

    What is printed?

    Python
    try:
        print("A")
        x = 1 / 0
        print("B")
    except ZeroDivisionError:
        print("C")
    finally:
        print("D")
  4. 4

    How do you create a custom exception?