Exception Handling

Things go wrong at runtime: a file might not exist, a user might type text where a number was expected, a list index might be out of range. Python's try/except mechanism lets a program detect these problems and respond gracefully instead of crashing outright.

The try/except Block

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

try:
    result = 10 / 0
except ZeroDivisionError:
    print("You can't divide by zero!")

# You can't divide by zero!
# the program keeps running instead of crashing

Catching a Specific Exception vs. a Bare except:

You can (and should) catch a specific exception type, so your code only reacts to the error it actually expects and knows how to handle:

try:
    age = int(input("Enter your age: "))
except ValueError:
    print("That's not a valid number.")

A bare except: (with no exception type) catches every possible error, including ones you never anticipated — typos in variable names, programming mistakes, even the user pressing Ctrl+C. This makes bugs much harder to find because real problems get silently swallowed along with the ones you meant to handle.

# Avoid this: hides real bugs along with expected errors
try:
    age = int(input("Enter your age: "))
except:
    print("Something went wrong.")

Note: Always prefer naming the specific exception(s) you expect. It documents your intent and lets unexpected bugs surface loudly instead of being hidden.

else and finally

Two optional clauses round out the full picture: else runs only when the try block completes with no exception, and finally always runs no matter what happened, making it the right place for cleanup code like closing a file or a network connection.

try:
    number = int(input("Enter a number: "))
    result = 100 / number
except ValueError:
    print("That wasn't a valid number.")
except ZeroDivisionError:
    print("Can't divide by zero.")
else:
    # only runs if no exception was raised above
    print(f"100 / {number} = {result}")
finally:
    # always runs, whether there was an error or not
    print("Done attempting the calculation.")

Raising Your Own Exceptions

Use raise to signal that something in your own code has gone wrong, using whichever built-in (or custom) exception type best describes the problem.

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

try:
    withdraw(100, 250)
except ValueError as e:
    print(f"Transaction failed: {e}")

# Transaction failed: Insufficient funds for this withdrawal.

Common Built-in Exception Types

ExceptionWhen it's raised
ValueErrorA value has the right type but an inappropriate value, e.g. int("abc").
TypeErrorAn operation is applied to a value of the wrong type, e.g. adding a string to an integer.
KeyErrorTrying to access a dictionary key that doesn't exist.
IndexErrorTrying to access a list index that is out of range.
ZeroDivisionErrorDividing a number by zero.
FileNotFoundErrorTrying to open a file that doesn't exist on disk.
Next steps: Head to OOP: Classes & Objects to start structuring your code around your own custom types.