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
| Exception | When it happens | Example |
|---|---|---|
ValueError | right type, wrong value | int("abc") |
TypeError | wrong type for the operation | "5" + 5 |
ZeroDivisionError | dividing by zero | 10 / 0 |
IndexError | list or string position out of range | [1, 2][5] |
KeyError | dictionary key not found | {"a": 1}["b"] |
NameError | name not defined | print(totl) |
FileNotFoundError | the file does not exist | open("nofile.txt") |
AttributeError | the 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
| Block | Runs when |
|---|---|
try | always: the code that might fail |
except | an exception of that type happened |
else | no exception happened |
finally | always, 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
tryand handle named exceptions inexcept. elseruns on success;finallyalways runs.except SomeError as egives you the message.raisesignals bad data; custom exceptions inherit fromException.
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 age5. 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.