Python Exception Handling – try, except, finally, raise Explained for Beginners

Python Exception Handling – try, except, finally, raise Explained for Beginners

Published on Code with Py  |  Category: Python Basics  |  Reading time: ~14 min

When you write real Python programs, errors will happen — a user enters wrong input, a file does not exist, or you divide by zero. Without handling these errors, your entire program crashes. Python gives you powerful tools — try, except, else, finally, and raise — to catch errors and keep your program running. This complete guide teaches you everything about Python Exception Handling with clear examples and real-life programs.

Table of Contents

  1. What is an Exception?
  2. Common Built-in Exceptions
  3. try and except – Catch an Error
  4. Catching Multiple Exceptions
  5. else Block – Runs When No Error Occurs
  6. finally Block – Always Runs
  7. raise – Throw Your Own Exception
  8. Custom Exception Classes
  9. Nested try-except
  10. Real-Life Projects
  11. Practice Problems
  12. FAQ

1. What is an Exception in Python?

An exception is an error that occurs while a program is running. When Python encounters something it cannot handle — like dividing by zero, accessing a missing file, or converting text to a number — it stops and raises an exception.

Without exception handling, even a small error will crash your entire program and show an ugly error message to the user. With exception handling, you can catch the error, show a friendly message, and let the program continue.

Without Exception Handling

Program crashes immediately. User sees a confusing error message. All work is lost. Remaining code never runs.

With Exception Handling

Error is caught gracefully. User sees a friendly message. Program continues safely. Remaining code still runs.

# Without handling — program crashes
num = int(input("Enter a number: "))
print(10 / num)
Enter a number: 0
ZeroDivisionError: division by zero

2. Common Built-in Exceptions in Python

Python has many built-in exceptions. These are the ones you will see most often as a beginner.

ExceptionWhen it occursExample
ZeroDivisionErrorDividing a number by zero10 / 0
ValueErrorWrong value type passed to a functionint("abc")
TypeErrorOperation on wrong data type"hi" + 5
NameErrorUsing an undefined variableprint(x) — x not defined
IndexErrorAccessing index that does not existlst[10] on 3-item list
KeyErrorAccessing a key not in a dictionaryd["age"] — key missing
FileNotFoundErrorOpening a file that does not existopen("x.txt")
AttributeErrorCalling method that does not exist"hello".push()
ImportErrorImporting a module that is not installedimport xyz
RecursionErrorToo many recursive callsMissing base case in recursion

3. try and except – Catch an Error

The try block contains code that might raise an error. The except block runs only if an error actually occurs. If no error happens, the except block is skipped completely.

Basic syntax

try:
    # Code that might cause an error
    risky_code()
except ExceptionType:
    # Code that runs if the error occurs
    handle_the_error()

Example 1 – Catching ZeroDivisionError

try:
    result = 10 / 0
    print(result)
except ZeroDivisionError:
    print("Error: Cannot divide by zero!")

print("Program continues normally.")
Error: Cannot divide by zero!
Program continues normally.

Example 2 – Catching ValueError from user input

try:
    age = int(input("Enter your age: "))
    print(f"You are {age} years old.")
except ValueError:
    print("Please enter a valid number!")
Enter your age: abc
Please enter a valid number!

Getting the error message using "as"

try:
    nums = [1, 2, 3]
    print(nums[10])
except IndexError as e:
    print(f"Error caught: {e}")
Error caught: list index out of range
Use except ExceptionName as e to capture the actual error message in the variable e. This is very useful for logging errors or showing detailed messages to users.

4. Catching Multiple Exceptions

A single try block can have multiple except blocks — one for each type of error. Python checks them from top to bottom and runs the first one that matches.

def safe_divide(a, b):
    try:
        result = a / b
        print(f"Result: {result}")
    except ZeroDivisionError:
        print("Error: Cannot divide by zero.")
    except TypeError:
        print("Error: Both values must be numbers.")

safe_divide(10, 2)
safe_divide(10, 0)
safe_divide(10, "x")
Result: 5.0
Error: Cannot divide by zero.
Error: Both values must be numbers.

Catching multiple exceptions in one line

try:
    value = int("abc")
except (ValueError, TypeError) as e:
    print(f"Caught: {e}")
Caught: invalid literal for int() with base 10: 'abc'

Catching any exception with bare except

try:
    x = int(input("Enter number: "))
    print(100 / x)
except Exception as e:
    print(f"Something went wrong: {e}")
Enter number: 0
Something went wrong: division by zero
Using a bare except Exception catches every type of error. This is useful as a catch-all fallback, but always try to catch specific exceptions first. Catching everything can hide bugs that you should actually fix.

5. else Block – Runs Only When No Error Occurs

The else block is optional and runs only if the try block completes without raising any exception. Think of it as "if everything went fine, do this."

try:
    num = int(input("Enter a number: "))
    result = 100 / num
except ZeroDivisionError:
    print("Cannot divide by zero!")
except ValueError:
    print("Please enter a valid number!")
else:
    print(f"Success! Result = {result}")
Enter a number: 5
Success! Result = 20.0
Use else to separate "what to do on success" from the main code. This makes your try-except structure much cleaner and easier to read.

6. finally Block – Always Runs No Matter What

The finally block always runs — whether an exception occurred or not. It is used for cleanup operations like closing a file, disconnecting from a database, or releasing resources.

try:
    print("Opening file...")
    result = 10 / 2
    print(f"Result: {result}")
except ZeroDivisionError:
    print("Division error!")
finally:
    print("Closing file... (always runs)")
Opening file...
Result: 5.0
Closing file... (always runs)
# finally runs even when there IS an error
try:
    result = 10 / 0
except ZeroDivisionError:
    print("Error caught!")
finally:
    print("This always executes.")
Error caught!
This always executes.

Complete structure — try + except + else + finally

try:
    num = int(input("Enter number: "))
    result = 100 / num
except ValueError:
    print("Invalid input!")
except ZeroDivisionError:
    print("Cannot divide by zero!")
else:
    print(f"Result = {result}")  # Only if no error
finally:
    print("Calculation finished.")  # Always
Enter number: 4
Result = 25.0
Calculation finished.
The full order is always: try → except → else → finally. The else runs when no exception occurs. The finally always runs last, no matter what happened above.

7. raise – Throw Your Own Exception

The raise statement lets you manually trigger an exception. You use it when you want to enforce rules in your program — for example, rejecting invalid input before it causes a problem later.

def check_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative!")
    if age > 150:
        raise ValueError("Age cannot exceed 150!")
    print(f"Valid age: {age}")

try:
    check_age(25)
    check_age(-5)
except ValueError as e:
    print(f"Error: {e}")
Valid age: 25
Error: Age cannot be negative!

8. Custom Exception Classes

You can create your own exception types by defining a class that inherits from Python's built-in Exception class. This makes your code more readable and your errors more meaningful.

# Define custom exception
class InsufficientBalanceError(Exception):
    def __init__(self, balance, amount):
        self.message = f"Cannot withdraw ₹{amount}. Balance is ₹{balance}."
        super().__init__(self.message)

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientBalanceError(balance, amount)
    balance -= amount
    print(f"Withdrawal successful. New balance: ₹{balance}")
    return balance

try:
    withdraw(1000, 500)
    withdraw(500,  800)
except InsufficientBalanceError as e:
    print(f"Bank Error: {e}")
Withdrawal successful. New balance: ₹500
Bank Error: Cannot withdraw ₹800. Balance is ₹500.
Custom exceptions make your programs more professional. Instead of a generic ValueError, you get a meaningful InsufficientBalanceError that tells you exactly what went wrong.

9. Nested try-except

You can place a try-except block inside another try-except block. This is useful when different parts of your code can fail in different ways.

try:
    num = int(input("Enter a number: "))
    try:
        result = 100 / num
        print(f"Result: {result}")
    except ZeroDivisionError:
        print("Inner error: Division by zero!")
except ValueError:
    print("Outer error: Please enter a valid integer!")
Enter a number: 0
Inner error: Division by zero!

10. Real-Life Projects

Project 1 – Safe Calculator

def calculator():
    try:
        a = float(input("First number:  "))
        b = float(input("Second number: "))
        op = input("Operation (+, -, *, /): ")

        if   op == "+": result = a + b
        elif op == "-": result = a - b
        elif op == "*": result = a * b
        elif op == "/":
            if b == 0: raise ZeroDivisionError
            result = a / b
        else:
            raise ValueError("Invalid operator")

    except ValueError as e:
        print(f"Input error: {e}")
    except ZeroDivisionError:
        print("Cannot divide by zero!")
    else:
        print(f"Answer: {result}")
    finally:
        print("Calculator session ended.\n")

calculator()
First number:  10
Second number: 0
Operation (+, -, *, /): /
Cannot divide by zero!
Calculator session ended.

Project 2 – Login System with Exception Handling

class InvalidCredentialsError(Exception):
    pass

def login(username, password):
    correct_user = "admin"
    correct_pass = "python123"

    if username != correct_user:
        raise InvalidCredentialsError("Username not found!")
    if password != correct_pass:
        raise InvalidCredentialsError("Incorrect password!")
    return True

for attempt in range(1, 4):
    try:
        u = input(f"Attempt {attempt} - Username: ")
        p = input(f"Attempt {attempt} - Password: ")
        login(u, p)
        print("Login successful!")
        break
    except InvalidCredentialsError as e:
        print(f"Error: {e}")
else:
    print("Account locked after 3 failed attempts.")
Attempt 1 - Username: admin
Attempt 1 - Password: wrongpass
Error: Incorrect password!
Attempt 2 - Username: admin
Attempt 2 - Password: python123
Login successful!

11. Practice Problems

Basic try-except

  1. Handle division by zero safely
  2. Handle invalid integer input from user
  3. Catch IndexError on a list
  4. Catch KeyError on a dictionary
  5. Print the actual error message using as

Multiple exceptions

  1. Handle both ValueError and TypeError
  2. Use else to run code only on success
  3. Use finally to print a closing message
  4. Handle 3 different exceptions in one try
  5. Use bare Exception to catch everything

raise and custom errors

  1. Raise ValueError if age is negative
  2. Raise custom error for invalid password
  3. Validate username length using raise
  4. Create NegativeNumberError class
  5. Raise and catch your custom exception

Real-life challenges

  1. Safe ATM withdrawal with exception
  2. Student marks validator (0–100 only)
  3. Safe file reader with FileNotFoundError
  4. Login with 3 attempt limit
  5. Calculator handling all error types
Always think about what can go wrong before writing your try block. Ask yourself — what inputs could break this code? Then write one except clause for each possible failure.

Quick Summary – What You Learned
  • An exception is a runtime error that stops your program if not handled
  • try contains the risky code — except handles the error if it occurs
  • Always catch specific exceptions before using a bare except Exception
  • else runs only when the try block completes without any error
  • finally always runs — perfect for cleanup like closing files or connections
  • raise lets you throw your own exception to enforce business rules
  • Custom exceptions make your code more readable and professional
  • Full order: try → except → else → finally

Frequently Asked Questions (FAQ)

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

An error is a broad term. Syntax errors happen before the program runs — Python cannot even parse the code. Exceptions happen during runtime — the code is valid but something goes wrong while executing. Exception handling only deals with runtime exceptions, not syntax errors.

Q2. Can I have multiple except blocks for one try block?

Yes. You can have as many except blocks as needed. Python checks them from top to bottom and executes the first one that matches the raised exception. After one except block runs, the rest are skipped.

Q3. What is the difference between else and finally in try-except?

The else block runs only when no exception occurred in the try block. The finally block always runs — whether an exception occurred or not. Use else for success logic and finally for cleanup that must always happen.

Q4. When should I use raise in Python?

Use raise when you want to enforce rules in your program. For example, if a function receives a negative age, you can raise a ValueError with a helpful message instead of letting the error appear later in a confusing place.

Q5. What is a custom exception and how do I create one?

A custom exception is a class you define that inherits from Python's built-in Exception class. You create it with class MyError(Exception): pass. Custom exceptions give your errors meaningful names and allow you to carry extra information about what went wrong.

Q6. Does finally run even if there is a return statement in try?

Yes. The finally block runs even if there is a return statement inside the try or except block. Python executes the finally block before actually returning. This guarantees cleanup code always runs.


Found this post helpful? Share it with someone learning Python. Drop your practice solutions in the comments — happy to review them!

"A good program does not just work when everything is perfect — it works gracefully even when things go wrong."

Comments

Popular posts from this blog

Python Strings – A Complete Guide for Beginners (with Examples)

Python Control Flow Statements – Complete Guide with Real-Life Examples

Python Loops Explained – for Loop and while Loop with Examples and Output