Python Tutorial › Errors › Exception Handling

Python Exception Handling

IntermediateLesson 34 of 4713 min read10 runnable examples

An exception is Python’s way of saying “something went wrong here”: a file is missing, a number cannot be parsed, a key does not exist. Handling exceptions lets your program recover, retry or report the problem clearly instead of crashing.

Advertisement
In this lesson
  • Read a traceback
  • Catch specific exceptions with try/except
  • Use else and finally correctly
  • Raise your own and define custom exceptions

What an exception looks like

When an error is not handled, Python stops and prints a traceback. Read it from the bottom: the last line names the exception and the message, and the lines above show where it happened.

Python
def average(values):    return sum(values) / len(values) print(average([10, 20]))print(average([]))
Output
15.0
Traceback (most recent call last):
  File "main.py", line 5, in <module>
    print(average([]))
          ~~~~~~~^^^^
  File "main.py", line 2, in average
    return sum(values) / len(values)
           ~~~~~~~~~~~~^~~~~~~~~~~~~
ZeroDivisionError: division by zero

Common built-in exceptions

ExceptionTypical cause
ValueErrorRight type, wrong value: int("abc")
TypeErrorWrong type: "2" + 2
KeyErrorMissing dict key
IndexErrorList index out of range
ZeroDivisionErrorDivision by zero
FileNotFoundErrorOpening a file that does not exist
AttributeErrorObject has no such attribute or method
NameErrorUsing a name that was never defined

try and except

Put the risky code in try. If an exception happens, Python jumps to the matching except block and continues after it.

Python
text = "12a"try:    number = int(text)    print("Parsed", number)except ValueError:    print(f"{text!r} is not a number")print("program continues")
Output
'12a' is not a number
program continues

Catch specific exceptions

Catch only the errors you expect and know how to handle. Use several except blocks, or a tuple, for different errors. as e gives you the exception object.

Python
def safe_divide(a, b):    try:        return a / b    except ZeroDivisionError:        return "cannot divide by zero"    except TypeError as e:        return f"bad input: {e}" print(safe_divide(10, 4))print(safe_divide(10, 0))print(safe_divide(10, "2"))
Output
2.5
cannot divide by zero
bad input: unsupported operand type(s) for /: 'int' and 'str'
Python
data = {"items": [1, 2]}for key, index in [("items", 5), ("users", 0)]:    try:        print(data[key][index])    except (KeyError, IndexError) as e:        print(type(e).__name__, e)
Output
IndexError list index out of range
KeyError 'users'
Watch out

Avoid a bare except: or except Exception: that swallows every error silently. It hides real bugs, including typos. If you must catch broadly, log the error.

else and finally

  • else runs only if the try block raised no exception.
  • finally runs always, error or not. Use it for clean-up such as closing a connection.
Python
def read_number(text):    try:        n = int(text)    except ValueError:        print("  invalid:", text)    else:        print("  valid:", n)    finally:        print("  done with", repr(text)) read_number("42")read_number("x")
Output
  valid: 42
  done with '42'
  invalid: x
  done with 'x'
Tip

For files and connections, a with statement does the clean-up for you, so you rarely need finally for that. See File Handling.

Raise an exception

Use raise to signal that something is wrong in your own code. Choose the built-in exception that fits best and write a helpful message.

Python
def set_age(age):    if not isinstance(age, int):        raise TypeError(f"age must be an int, got {type(age).__name__}")    if age < 0:        raise ValueError("age cannot be negative")    return age for value in [30, -1, "30"]:    try:        print("ok", set_age(value))    except (TypeError, ValueError) as e:        print("rejected:", e)
Output
ok 30
rejected: age cannot be negative
rejected: age must be an int, got str

A bare raise inside except re-raises the same exception after you have logged it:

Python
try:    {}["missing"]except KeyError:    print("logging the error, then passing it on")    raise
Output
logging the error, then passing it on
Traceback (most recent call last):
  File "main.py", line 2, in <module>
    {}["missing"]
    ~~^^^^^^^^^^^
KeyError: 'missing'

Custom exceptions

Create your own exception classes by inheriting from Exception. They make errors easy to recognise and catch.

Python
class InsufficientFundsError(Exception):    def __init__(self, balance, amount):        super().__init__(f"need {amount}, only {balance} available")        self.balance = balance        self.amount = amount def withdraw(balance, amount):    if amount > balance:        raise InsufficientFundsError(balance, amount)    return balance - amount try:    withdraw(500, 800)except InsufficientFundsError as e:    print("Declined:", e)    print("Short by", e.amount - e.balance)
Output
Declined: need 800, only 500 available
Short by 300

Exception chaining

raise ... from ... keeps the original error attached, so the traceback shows both:

Python
def load_port(config):    try:        return int(config["port"])    except (KeyError, ValueError) as e:        raise RuntimeError("invalid config: port") from e load_port({"port": "eighty"})
Output
Traceback (most recent call last):
  File "main.py", line 3, in load_port
    return int(config["port"])
ValueError: invalid literal for int() with base 10: 'eighty'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "main.py", line 7, in <module>
    load_port({"port": "eighty"})
    ~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "main.py", line 5, in load_port
    raise RuntimeError("invalid config: port") from e
RuntimeError: invalid config: port

Retry pattern

Python
attempts = iter(["timeout", "timeout", "ok"]) def call_api():    result = next(attempts)    if result == "timeout":        raise TimeoutError("server did not answer")    return "200 OK" for attempt in range(1, 4):    try:        print(call_api(), "on attempt", attempt)        break    except TimeoutError as e:        print(f"attempt {attempt}: {e}")
Output
attempt 1: server did not answer
attempt 2: server did not answer
200 OK on attempt 3

Check your understanding

Predict the output

def f():    try:        return "try"    finally:        print("finally runs first") print(f())

Quick quiz

1. When does the else block of a try statement run?

2. Which exception does int("abc") raise?

3. How do you create a custom exception?

4. Why is a bare except: discouraged?

Exercise: safe average

Write safe_average(items) that converts every item to float, skips items that cannot be converted (printing a warning), and returns 0 for an empty result.

Input
["10", "abc", "20.5", None, "30"]
Expected output
skipping 'abc'
skipping None
20.166666666666668
0
Hint 1

Catch both ValueError (bad text) and TypeError (None).

Program Practice

Python exception handling FAQ

What is the difference between an error and an exception in Python?

In everyday use they mean the same thing. Technically, syntax errors stop the file from running at all, while exceptions happen while the program runs and can be caught.

Can I catch multiple exceptions in one except?

Yes. Use a tuple: except (ValueError, TypeError) as e:.

What is the purpose of finally in Python?

finally holds clean-up code that must run whether or not an exception happened, such as closing a connection.

How do I print the full traceback of a caught exception?

Use the traceback module: traceback.print_exc() inside the except block, or logging.exception("message").

Leave a Comment