Exception Handling in Python
Catch runtime errors with try and except, use else and finally, raise your own exceptions, and recognise Python's common built-in exceptions.
Learning objectives
- βTell syntax errors apart from exceptions that happen while a program runs
- βHandle errors with try, except, else and finally
- βCatch specific exceptions and read their messages
- βRaise an exception yourself with raise
π‘ Key points
- A syntax error stops the program before it starts. An exception happens while it is running, such as dividing by zero.
- Put the code that might fail inside try; put the recovery code inside except.
- Catch specific exceptions (except ValueError:). A bare except: also catches mistakes you did not expect and hides real bugs.
- else runs only when the try block raised no exception; finally runs every time, error or not.
- except ValueError as e: gives you the exception object; print(e) shows its message.
- raise ValueError('message') throws an exception yourself, for example to reject impossible data.
- Common built-ins: ZeroDivisionError, ValueError, TypeError, NameError, IndexError, KeyError, FileNotFoundError.
π» Code examples(6)
a, b = 10, 0
try:
print(a / b)
except ZeroDivisionError:
print("Cannot divide by zero")
print("Program continues")
Cannot divide by zero Program continues
while True:
try:
marks = int(input("Enter marks: "))
break # valid: leave loop
except ValueError:
print("Not a number, try again.")
print("You entered", marks)
Enter marks: ninety Not a number, try again. Enter marks: 90 You entered 90
marks = [87, 92, 76]
for pos in ["1", "x", "5"]:
try:
print(marks[int(pos)])
except ValueError as e:
print("ValueError:", e)
except IndexError as e:
print("IndexError:", e)
92 ValueError: invalid literal for int() with base 10: 'x' IndexError: list index out of range
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("Error: division by zero")
else:
print("Result:", result)
finally:
print("Done with", a, "/", b)
divide(10, 4)
divide(5, 0)
Result: 2.5 Done with 10 / 4 Error: division by zero Done with 5 / 0
def set_marks(m):
if not 0 <= m <= 100:
raise ValueError(f"marks must be 0-100, got {m}")
return m
for m in [88, 105]:
try:
print("Saved", set_marks(m))
except ValueError as e:
print("Rejected:", e)
Saved 88 Rejected: marks must be 0-100, got 105
tests = [
lambda: 10 / 0,
lambda: int("abc"),
lambda: "5" + 5,
lambda: [1, 2][5],
lambda: {"a": 1}["b"],
lambda: total_marks,
lambda: open("missing.txt"),
]
for t in tests:
try:
t()
except Exception as e:
print(type(e).__name__, "-", e)
ZeroDivisionError - division by zero ValueError - invalid literal for int() with base 10: 'abc' TypeError - can only concatenate str (not "int") to str IndexError - list index out of range KeyError - 'b' NameError - name 'total_marks' is not defined FileNotFoundError - [Errno 2] No such file or directory: 'missing.txt'
π― Practice
Q1. Which block always runs, whether an exception happened or not?+
finally.
Q2. What is printed? try: print(int('7') + 1) except ValueError: print('bad') else: print('ok')+
8 and then ok. No exception occurred, so the else block also runs.
Q3. Name the exception raised by: (a) [1, 2, 3][3] (b) int('12a') (c) 5 + 'x' (d) {'a': 1}['z']+
(a) IndexError (b) ValueError (c) TypeError (d) KeyError
Q4. Write a function sqrt_of(n) that raises ValueError if n is negative, otherwise returns n ** 0.5.+
def sqrt_of(n): if n < 0: raise ValueError('n must not be negative') return n ** 0.5
Q5. Why is a bare except: (with no exception name) a bad idea?+
It catches every error, including typos such as a misspelled variable (NameError), so real bugs are hidden. Name the exceptions you expect.
π Notes
Reading a traceback
When an exception is not handled, Python prints a traceback and stops. For a file demo.py whose second line is x = 10 / 0, Python 3.12 prints (with the full path of your file in place of demo.py):
Traceback (most recent call last):
File "demo.py", line 2, in <module>
x = 10 / 0
~~~^~~
ZeroDivisionError: division by zero
Read it from the bottom: the last line names the exception and gives the message, and the lines above it show the file, the line number and (in newer versions) markers under the part that failed.
How the blocks fit together
try:
... # code that might fail
except SomeError:
... # runs only if SomeError happened
else:
... # runs only if nothing went wrong
finally:
... # runs every time
trymust be followed by at least oneexceptor afinally.- You can catch several types in one block:
except (ValueError, TypeError):. - Order matters: put specific exceptions before general ones like
Exception.
Errors you cannot catch this way
A syntax error (a missing colon, an unclosed bracket) is found before the program runs, so try never gets a chance. Logical errors (a wrong formula giving a wrong answer) raise nothing at all. Exception handling only helps with problems that happen at run time.
Common mistakes
- Too much code inside try: keep only the risky lines there, so you know exactly what failed.
- Swallowing errors silently:
except: passmakes bugs invisible. At least print a message. - Catching the wrong type:
int("abc")raises ValueError, not TypeError. - Using exceptions for normal checks:
if b != 0:is clearer than catching ZeroDivisionError every time.
Next: Day 17 β Text File Handling, where FileNotFoundError and finally become very useful.