LearnContact
Lesson 3330 min read

Exception Handling

Learn how to catch and handle runtime errors gracefully in Python using try, except, else, finally, and raise.

Introduction

No matter how carefully you write code, things can go wrong at runtime: a user might type text instead of a number, a file might be missing, or a network request might fail. These runtime problems are called exceptions, and if left unhandled, they crash your program immediately.

Python gives you a clean way to anticipate and handle these problems using try, except, else, and finally. This lesson teaches you how to catch errors gracefully, respond to specific problems differently, and raise your own errors when something is wrong.

What You Will Learn
  • How try and except catch runtime errors before they crash your program.
  • How to catch specific exception types like ValueError and ZeroDivisionError.
  • What the else and finally clauses do and when to use them.
  • How to raise your own exceptions using raise.
  • Why catching specific exceptions is better than a bare except.

Why Exception Handling Matters

Without exception handling, a single unexpected error stops your entire program dead, printing a scary-looking traceback and losing any unsaved work. Exception handling lets your program anticipate likely problems and respond sensibly — showing a friendly message, retrying, or skipping the problem — instead of crashing.

Prevents Crashes

Catch errors before they stop your entire program.

Friendlier Messages

Show a helpful message instead of a raw traceback.

Recovery

Retry an operation or fall back to a default value.

Cleanup

Guarantee that resources like files or connections are closed properly.

try and except

Code that might fail goes inside a try block. If an error occurs, Python immediately jumps to the matching except block instead of crashing the program.

basic_try.py
try:
    number = int(input("Enter a number: "))
    print("You entered:", number)
except:
    print("That was not a valid number!")
Output (user enters "abc")
That was not a valid number!

The bare except: above catches any error at all, which is convenient but hides useful information about exactly what went wrong. The next section shows a better approach.

Catching Specific Exception Types

Python has many built-in exception types, each describing a specific kind of problem. Catching the specific type lets you respond appropriately to each situation, and lets unexpected errors still surface clearly instead of being silently hidden.

ExceptionCommon Cause
ValueErrorConverting invalid data, like int('abc')
ZeroDivisionErrorDividing a number by zero
FileNotFoundErrorOpening a file that does not exist
KeyErrorAccessing a dictionary key that does not exist
TypeErrorUsing an operation on the wrong data type
specific_except.py
try:
    number = int(input("Enter a number: "))
    result = 100 / number
    print("Result:", result)
except ValueError:
    print("That is not a valid whole number.")
except ZeroDivisionError:
    print("You cannot divide by zero.")
Output (user enters "0")
You cannot divide by zero.

Python checks each except clause in order and runs the first one that matches the type of exception that was raised.

file_and_dict.py
data = {"name": "Alex", "age": 16}

try:
    with open("missing.txt", "r") as file:
        print(file.read())
    print(data["email"])
except FileNotFoundError:
    print("The file could not be found.")
except KeyError:
    print("That key does not exist in the dictionary.")
Output
The file could not be found.

Notice that as soon as the FileNotFoundError happens, Python jumps straight to its except block — the print(data["email"]) line never even runs, so its KeyError never gets a chance to occur here.

The else Clause

An optional else block runs only if the try block completed with no exceptions at all. It is useful for code that should run only on success, keeping it separate from the risky code in try.

else_clause.py
try:
    number = int("42")
except ValueError:
    print("Conversion failed.")
else:
    print("Conversion succeeded! Number is:", number)
Output
Conversion succeeded! Number is: 42

The finally Clause

A finally block always runs, whether an exception occurred or not, and even if the exception was not caught by any except clause. It is the ideal place for cleanup code, like closing a file or a network connection.

finally_clause.py
try:
    number = int("not a number")
except ValueError:
    print("Could not convert to a number.")
finally:
    print("This always runs, no matter what.")
Output
Could not convert to a number.
This always runs, no matter what.
try / except / else / finally, In Order
  • try: code that might raise an exception.
  • except: runs only if a matching exception occurred.
  • else: runs only if no exception occurred.
  • finally: always runs, no matter what happened.

Raising Exceptions with raise

Sometimes you want to signal an error yourself, rather than waiting for Python to raise one naturally. The raise keyword lets you trigger an exception on purpose, which is especially useful inside functions that validate their input.

raise_example.py
def withdraw(balance, amount):
    if amount > balance:
        raise ValueError("Insufficient funds for this withdrawal.")
    return balance - amount

try:
    new_balance = withdraw(100, 250)
except ValueError as error:
    print("Error:", error)
Output
Error: Insufficient funds for this withdrawal.

Using as error captures the exception object so you can inspect or print its message, rather than just knowing that some error occurred.

Custom Exceptions (A Quick Preview)

Python also lets you define your own exception types by creating a class that inherits from Exception. This is a preview only — classes and inheritance are covered in depth starting with the next few lessons on object-oriented programming.

custom_exception_preview.py
class InsufficientFundsError(Exception):
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFundsError("Not enough money in the account.")
    return balance - amount

try:
    withdraw(100, 500)
except InsufficientFundsError as error:
    print("Custom error caught:", error)
Output
Custom error caught: Not enough money in the account.

Common Mistakes

Avoid These Mistakes
  • Using a bare except: that silently swallows every error, including bugs you did not expect.
  • Catching Exception broadly when a specific exception type would give clearer, safer handling.
  • Putting too much code inside try, making it unclear exactly which line might fail.
  • Forgetting that finally runs even after a return statement inside try or except.
  • Using exceptions for normal control flow instead of for truly exceptional situations.

Best Practices

  • Catch specific exception types like ValueError or KeyError instead of a bare except.
  • Keep the try block as small as possible, containing only the risky code.
  • Use finally for cleanup that must always happen, like closing files or connections.
  • Give raised exceptions clear, descriptive messages that explain what went wrong.
  • Avoid hiding real bugs by catching exceptions you do not know how to handle.

Frequently Asked Questions

What is the difference between except: and except Exception:?

A bare except: catches literally everything, including things like keyboard interrupts. except Exception: is slightly more controlled but still broad. Catching specific types like ValueError is the safest, clearest approach.

Does finally run if I use return inside try?

Yes. The finally block always runs before the function actually returns, even if try or except contains a return statement.

Can I catch multiple exception types in one except clause?

Yes, by grouping them in a tuple, like except (ValueError, TypeError): to handle both the same way.

When should I use raise instead of just returning an error value?

Use raise when a problem is truly exceptional and the calling code should be forced to notice it, rather than silently continuing with a default or error value.

Do I need to learn classes before writing custom exceptions?

Yes, a solid understanding of classes and inheritance helps you write custom exceptions properly. You will get there very soon, starting with the OOP lessons right after this one.

Key Takeaways

  • try and except let you catch runtime errors before they crash your program.
  • Catching specific exception types like ValueError, ZeroDivisionError, FileNotFoundError, and KeyError is safer than a bare except.
  • else runs only when no exception occurred; finally always runs.
  • raise lets you trigger your own exceptions, often to validate input inside functions.
  • Custom exception classes are possible by inheriting from Exception, a topic you will deepen once classes are covered.

Summary

Exception handling lets your Python programs anticipate and gracefully recover from runtime errors instead of crashing. You learned how try and except catch problems, how else and finally control what runs and when, and how raise lets you signal your own errors.

Lesson 33 Completed
  • You can catch and handle runtime errors using try and except.
  • You know how to target specific exception types.
  • You understand else, finally, and raise.
  • You are ready to begin object-oriented programming.
Next Lesson →

Object-Oriented Programming (OOP)