Questions or a demo for your school?Student? Sit a test
BrainBit Infotech TeachingStart free

Python notes · Topic 13 of 22 · 4 min read

Python errors and exception handling

Python exception handling notes: common exceptions, try and except, else and finally, catching several errors, raise, custom exceptions and reading tracebacks.

Errors and exceptions

A syntax error means Python cannot understand your code, so nothing runs. An exception is an error that happens *while* the program runs: the code is valid, but something goes wrong, such as dividing by zero or opening a missing file. Unhandled, an exception stops the program.

Common exceptions you will meet

ExceptionWhen it happensExample
ValueErrorright type, wrong valueint("abc")
TypeErrorwrong type for the operation"5" + 5
ZeroDivisionErrordividing by zero10 / 0
IndexErrorlist or string position out of range[1, 2][5]
KeyErrordictionary key not found{"a": 1}["b"]
NameErrorname not definedprint(totl)
FileNotFoundErrorthe file does not existopen("nofile.txt")
AttributeErrorthe object has no such method"hi".push("!")

try / except: catch the error, keep running

try:
    age = int(input("Age: "))
    print("Next year:", age + 1)
except ValueError:
    print("Please type a number")
print("Program continues")

If a line inside try raises a ValueError, Python jumps straight to the matching except block instead of crashing.

Always name the exception. A bare except: catches everything, including typing mistakes in your own code, and hides real bugs.

else and finally

try:
    f = open("marks.txt", encoding="utf-8")
except FileNotFoundError:
    print("File missing")
else:
    print(f.read())          # runs only if there was no exception
    f.close()
finally:
    print("Done")            # always runs, error or not
BlockRuns when
tryalways: the code that might fail
exceptan exception of that type happened
elseno exception happened
finallyalways, at the end: clean-up code

Catching several errors and reading the message

try:
    a = int(input("A: "))
    b = int(input("B: "))
    print(a / b)
except ValueError:
    print("Numbers only")
except ZeroDivisionError:
    print("B cannot be zero")

try:
    x = [1, 2, 3][9]
except (IndexError, KeyError) as e:
    print("Problem:", e)          # e holds the error message

raise: create your own error when data is wrong

def set_marks(marks):
    if not 0 <= marks <= 100:
        raise ValueError(f"marks must be 0-100, got {marks}")
    return marks

raise stops the function and sends the exception to whoever called it. Raise errors as soon as you spot bad data, instead of carrying on with wrong values.

Custom exceptions

For your own apps, make an exception class that inherits from Exception:

class InsufficientBalance(Exception):
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientBalance(f"balance is only Rs {balance}")
    return balance - amount

try:
    withdraw(500, 800)
except InsufficientBalance as e:
    print("Cannot withdraw:", e)

Keep asking until the input is valid

def read_int(prompt):
    while True:
        try:
            return int(input(prompt))
        except ValueError:
            print("That is not a whole number, try again")

marks = read_int("Marks: ")

This small function is worth keeping: almost every program that asks for numbers needs it.

Reading a traceback

When an exception is not caught, Python prints a traceback: the chain of calls that led to the error.

Traceback (most recent call last):
  File "bill.py", line 8, in <module>
    print(average([]))
  File "bill.py", line 2, in average
    return sum(nums) / len(nums)
ZeroDivisionError: division by zero

Read it from the bottom: the last line is the error, and the line above it is where it happened. Lines higher up show which call led there: here, average([]) was called with an empty list.

Common mistakes

  • Using a bare except: that hides every bug.
  • Putting too much code inside try, so you cannot tell which line failed.
  • Catching an exception and doing nothing (pass), so errors disappear silently.
  • Confusing SyntaxError (code cannot run) with exceptions (code fails while running).

Key points

  • Put risky code in try and handle named exceptions in except.
  • else runs on success; finally always runs.
  • except SomeError as e gives you the message.
  • raise signals bad data; custom exceptions inherit from Exception.

Practice questions

1. Which exception does each line raise: int("12a"), [1, 2][2], {"x": 1}["y"]?

ValueError, IndexError and KeyError.

2. Write a safe division that prints "Cannot divide by zero" instead of crashing.
try:
    print(a / b)
except ZeroDivisionError:
    print("Cannot divide by zero")
3. In what order do the blocks print if no error happens?
try:
    print("A")
except ValueError:
    print("B")
else:
    print("C")
finally:
    print("D")

A, C, D. B is skipped because nothing failed.

4. Make a function check_age(age) that raises a ValueError for ages below 0 or above 120.
def check_age(age):
    if age < 0 or age > 120:
        raise ValueError("age must be 0-120")
    return age
5. Why is except: with no exception name a bad idea?

It catches every error, including mistakes like a misspelt variable name, so real bugs are hidden.