Error Handling in Python

Try / Except

Why Error Handling?

Run this program:


    num = int(input("Enter a number: "))
    print(f"Double is {num * 2}")

Type "hello" instead of a number. Your program crashes:

ValueError: invalid literal for int() with base 10: 'hello'

Now imagine this is a real app with 10,000 users. One wrong input — entire program crashes. That's a disaster.

Error handling lets your program deal with errors gracefully instead of crashing.


Types of Errors in Python

Before learning how to handle errors, understand the two main types:

1. Syntax Errors — Before program runs

Code is written wrong. Python catches these before even running:

if age >= 18     # SyntaxError: missing colon
print "Hello"    # SyntaxError: Python 2 style

You fix these by correcting your code. Error handling cannot catch syntax errors.

2. Exceptions — While program runs

Code is correct but something goes wrong during execution:

int("hello")          # ValueError
10 / 0                # ZeroDivisionError
open("ghost.txt")     # FileNotFoundError
name                  # NameError — variable doesn't exist

These are what try/except handles.


Basic try/except


    try:
        # code that might cause an error
        num = int(input("Enter a number: "))
        print(f"Double is {num * 2}")
    except:
        # code that runs if ANY error occurs
        print("Something went wrong! Please enter a valid number.")

Now if user types "hello":

Enter a number: hello
Something went wrong! Please enter a valid number.

Program does NOT crash. It handles the error and continues.


Catching Specific Exceptions

Using bare except: catches everything — too broad. Better to catch specific errors:


    try:
        num = int(input("Enter a number: "))
        result = 100 / num
        print(f"Result: {result}")
    except ValueError:
        print("Invalid input — please enter a number, not text")
    except ZeroDivisionError:
        print("Cannot divide by zero!")

Now:

  • Type "hello" → ValueError message
  • Type "0" → ZeroDivisionError message
  • Type "5" → works perfectly, prints 20.0

Common Exception Types

Exception

When it happens

ValueError

Wrong type of value — int("hello")

ZeroDivisionError

Dividing by zero — 10 / 0

FileNotFoundError

File doesn't exist

IndexError

List index out of range — list[99]

KeyError

Dictionary key not found — dict["missing"]

TypeError

Wrong type operation — "hello" + 5

NameError

Variable doesn't exist

AttributeError

Method doesn't exist on object



else and finally

try/except has two optional extra blocks:


    try:
        num = int(input("Enter a number: "))
        result = 100 / num
    except ValueError:
        print("Please enter a valid number")
    except ZeroDivisionError:
        print("Cannot divide by zero")
    else:
        # runs ONLY if no exception occurred
        print(f"Success! Result is {result}")
    finally:
        # runs ALWAYS — whether error or not
        print("Program finished")

Test with different inputs:

Input "hello":

Please enter a valid number
Program finished

Input "0":

Cannot divide by zero
Program finished

Input "5":

Success! Result is 20.0
Program finished

else — success path, runs only when no error finally — cleanup code, runs no matter what. Used for closing files, database connections, etc.


Getting Error Details

You can capture the actual error message:


    try:
        num = int("hello")
    except ValueError as e:
        print(f"Error occurred: {e}")

Output:

Error occurred: invalid literal for int() with base 10: 'hello'

The as e gives you the error object. e contains the error message. Very useful for debugging.


Catching Multiple Exceptions Together


    try:
        num = int(input("Enter number: "))
        result = 100 / num
    except (ValueError, ZeroDivisionError) as e:
        print(f"Input error: {e}")

Group exceptions in a tuple when you want to handle them the same way.


Nested try/except


    try:
        with open("data.txt", "r") as file:
            try:
                num = int(file.read())
                print(f"Number from file: {num}")
            except ValueError:
                print("File contains invalid data")
    except FileNotFoundError:
        print("data.txt not found")

Outer try handles file errors, inner try handles data errors.


Raising Your Own Exceptions

You can throw errors on purpose using raise:


    def set_age(age):
        if age < 0:
            raise ValueError("Age cannot be negative")
        if age > 150:
            raise ValueError("Age cannot be more than 150")
        return age

    try:
        age = set_age(-5)
    except ValueError as e:
        print(f"Invalid age: {e}")

Output:

Invalid age: Age cannot be negative

This is how you enforce rules in your functions. If someone passes invalid data, you raise an error with a clear message.


Real World Example 1 — Bulletproof Input

This is something every real program needs — keep asking until valid input:


    def get_integer(prompt):
        while True:
            try:
                value = int(input(prompt))
                return value
            except ValueError:
                print("Invalid input. Please enter a whole number.")

    def get_positive_integer(prompt):
        while True:
            value = get_integer(prompt)
            if value > 0:
                return value
            print("Please enter a positive number.")

    def get_float(prompt):
        while True:
            try:
                value = float(input(prompt))
                return value
            except ValueError:
                print("Invalid input. Please enter a number.")


    # Now use these safe input functions
    age = get_positive_integer("Enter your age: ")
    salary = get_float("Enter your salary: ")

    print(f"\nAge: {age}")
    print(f"Salary: Rs.{salary:,.2f}")

These helper functions are something you'd keep and reuse in every project. They never crash no matter what the user types.


Real World Example 2 — Safe File Handler


    def read_file(filename):
        try:
            with open(filename, "r") as file:
                return file.read()
        except FileNotFoundError:
            print(f"Error: '{filename}' not found")
            return None
        except PermissionError:
            print(f"Error: No permission to read '{filename}'")
            return None

    def write_file(filename, content):
        try:
            with open(filename, "w") as file:
                file.write(content)
            print(f"Successfully saved to '{filename}'")
            return True
        except PermissionError:
            print(f"Error: No permission to write '{filename}'")
            return False
        except Exception as e:
            print(f"Unexpected error: {e}")
            return False


    content = read_file("notes.txt")
    if content:
        print(content)

    write_file("output.txt", "Hello from Python!")


Real World Example 3 — Calculator with Full Error Handling

Let's upgrade the calculator we built in Stage 3:


    def divide(a, b):
        if b == 0:
            raise ZeroDivisionError("Cannot divide by zero")
        return a / b

    def get_number(prompt):
        while True:
            try:
                return float(input(prompt))
            except ValueError:
                print("Please enter a valid number")

    def calculate():
        print("=== Safe Calculator ===")

        num1 = get_number("Enter first number: ")
        num2 = get_number("Enter second number: ")

        print("\nChoose operation:")
        print("1. Add")
        print("2. Subtract")
        print("3. Multiply")
        print("4. Divide")

        choice = input("Choice: ")

        try:
            if choice == "1":
                result = num1 + num2
            elif choice == "2":
                result = num1 - num2
            elif choice == "3":
                result = num1 * num2
            elif choice == "4":
                result = divide(num1, num2)
            else:
                raise ValueError("Invalid operation selected")

            print(f"\nResult: {result:.4f}")

        except ZeroDivisionError as e:
            print(f"Math error: {e}")
        except ValueError as e:
            print(f"Input error: {e}")
        except Exception as e:
            print(f"Unexpected error: {e}")

    calculate()

This calculator will never crash — every possible error is handled.


Exception Hierarchy

In Python all exceptions are organized in a hierarchy. Exception is the parent of almost all exceptions:

Exception
├── ValueError
├── TypeError
├── ZeroDivisionError
├── FileNotFoundError
├── IndexError
├── KeyError
└── ... and many more

So catching Exception catches almost everything:


    try:
        # some code
    except Exception as e:
        print(f"Something went wrong: {e}")

Use this as a last resort — always try to catch specific exceptions first.


Best Practices

1 — Be specific, not general:


    # Bad — too broad
    except:
        pass

    # Good — specific
    except ValueError:
        print("Invalid value")

2 — Never silently swallow errors:


    # Bad — error is hidden, you'll never know something went wrong
    try:
        risky_operation()
    except:
        pass

    # Good — at least log it
    try:
        risky_operation()
    except Exception as e:
        print(f"Error: {e}")

3 — Keep try blocks small:


    # Bad — too much code in try
    try:
        name = input("Name: ")
        age = int(input("Age: "))
        city = input("City: ")
        process_data(name, age, city)
        save_to_file(name)
    except ValueError:
        pass  # which line caused it?

    # Good — only the risky part
    name = input("Name: ")
    try:
        age = int(input("Age: "))
    except ValueError:
        print("Age must be a number")
    city = input("City: ")


Quick Reference


    try:
        # risky code here
    except ValueError:
        # handle value errors
    except ZeroDivisionError:
        # handle division by zero
    except FileNotFoundError:
        # handle missing files
    except (TypeError, KeyError) as e:
        # handle multiple exceptions
        print(f"Error: {e}")
    except Exception as e:
        # catch all other exceptions
        print(f"Unexpected: {e}")
    else:
        # runs only if NO exception occurred
        print("Success!")
    finally:
        # always runs
        print("Done")

    # Raise your own exception
    raise ValueError("Your custom error message")


Exercise 🏋️

Build a Student Marks Manager with full error handling:

  1. Store student data in a file marks.txt in this format:
Rahul,85,90,78
Priya,92,88,95
Gagan,75,82,79
  1. Build a menu:
=== Marks Manager ===
1. View all students
2. Add student
3. Get student average
4. Delete student
5. Exit

Handle ALL these errors properly:

  • File not found when loading
  • Invalid marks input (non-numbers)
  • Student not found when searching
  • Duplicate student name when adding
  • Invalid menu choice

This exercise combines file handling from Stage 6 with error handling from Stage 7 — two stages in one project!

File Handling In Python

Reading and Writing Files

Why File Handling?

So far everything in your programs is temporary. When the program closes — all data is gone.

Contact book banaya? Program band kiya — sab contacts gone. Shopping cart banaya? Program band kiya — sab items gone.

File handling lets you permanently save data to a file on your computer so it persists even after the program closes.


How File Handling Works

Three basic operations:

  • Write — save data to a file
  • Read — load data from a file
  • Append — add more data to existing file

Opening a File — open()

file = open("filename.txt", "mode")

Modes:

Mode Meaning
"r" Read — open existing file to read
"w" Write — create new file or overwrite existing
"a" Append — add to end of existing file
"x" Create — create new file, error if already exists

Writing to a File


    file = open("notes.txt", "w")
    file.write("Hello, this is my first file!\n")
    file.write("I am learning Python file handling.\n")
    file.write("This is line 3.\n")
    file.close()

This creates a file called notes.txt in the same folder as your Python file and writes those three lines into it.

Important — always call file.close() when done. If you forget, data might not save properly.


The Better Way — with Statement

Instead of manually closing, use with — it automatically closes the file when done:


    with open("notes.txt", "w") as file:
        file.write("Hello, this is my first file!\n")
        file.write("I am learning Python file handling.\n")
        file.write("This is line 3.\n")

    # file is automatically closed here
    print("File written successfully!")

Always use with — it's cleaner, safer, and the standard way in Python.


Reading a File

Read entire file at once


    with open("notes.txt", "r") as file:
        content = file.read()
        print(content)

Output:

Hello, this is my first file!
I am learning Python file handling.
This is line 3.

Read line by line — readline()


    with open("notes.txt", "r") as file:
        line1 = file.readline()    # reads first line
        line2 = file.readline()    # reads second line
        print(line1)
        print(line2)


Read all lines into a list — readlines()


    with open("notes.txt", "r") as file:
        lines = file.readlines()   # returns a list of all lines
        print(lines)

Output:

['Hello, this is my first file!\n', 'I am learning Python file handling.\n', 'This is line 3.\n']

Notice each line has \n at the end. Use .strip() to remove it:


    with open("notes.txt", "r") as file:
        lines = file.readlines()

    for line in lines:
        print(line.strip())    # removes \n from each line


Best way to read — Loop directly


    with open("notes.txt", "r") as file:
        for line in file:
            print(line.strip())

This is memory efficient — reads one line at a time instead of loading the whole file at once. Best for large files.


Appending to a File

"w" mode overwrites everything. Use "a" to add to existing content:


    # First write
    with open("notes.txt", "w") as file:
        file.write("Line 1\n")
        file.write("Line 2\n")

    # Append more — existing content stays
    with open("notes.txt", "a") as file:
        file.write("Line 3\n")
        file.write("Line 4\n")

    # Read and verify
    with open("notes.txt", "r") as file:
        print(file.read())

Output:

Line 1
Line 2
Line 3
Line 4

File Does Not Exist — Handling Errors

If you try to read a file that doesn't exist:


    with open("missing.txt", "r") as file:    # FileNotFoundError!
        content = file.read()

Handle it properly:


    try:
        with open("missing.txt", "r") as file:
            content = file.read()
            print(content)
    except FileNotFoundError:
        print("File not found! Please check the filename.")

We'll cover error handling in full detail in Stage 7. For now just know this pattern exists.


Checking if File Exists


    import os

    if os.path.exists("notes.txt"):
        print("File exists")
    else:
        print("File does not exist")

import os gives you access to operating system features like checking files, creating folders, etc. We'll cover imports properly in Stage 8.


Writing Multiple Lines at Once — writelines()


    lines = [
        "First line\n",
        "Second line\n",
        "Third line\n"
    ]

    with open("notes.txt", "w") as file:
        file.writelines(lines)

Note — writelines() does NOT automatically add \n. You have to include it in each string yourself.


Real World Example 1 — Save and Load a To-Do List


    FILENAME = "todos.txt"

    def load_todos():
        todos = []
        try:
            with open(FILENAME, "r") as file:
                for line in file:
                    todos.append(line.strip())
        except FileNotFoundError:
            pass    # file doesn't exist yet — that's fine, start with empty list
        return todos

    def save_todos(todos):
        with open(FILENAME, "w") as file:
            for todo in todos:
                file.write(todo + "\n")

    def show_todos(todos):
        if len(todos) == 0:
            print("No tasks yet!")
        else:
            print("\n=== Your To-Do List ===")
            for i, todo in enumerate(todos, 1):
                print(f"{i}. {todo}")

    def add_todo(todos):
        task = input("Enter task: ")
        todos.append(task)
        save_todos(todos)
        print("Task added and saved!")

    def delete_todo(todos):
        show_todos(todos)
        if len(todos) > 0:
            num = int(input("Enter task number to delete: "))
            if 1 <= num <= len(todos):
                removed = todos.pop(num - 1)
                save_todos(todos)
                print(f"Deleted: {removed}")
            else:
                print("Invalid number")


    # Load existing todos when program starts
    todos = load_todos()

    while True:
        print("\n=== To-Do App ===")
        print("1. View tasks")
        print("2. Add task")
        print("3. Delete task")
        print("4. Exit")

        choice = input("Enter choice: ")

        if choice == "1":
            show_todos(todos)
        elif choice == "2":
            add_todo(todos)
        elif choice == "3":
            delete_todo(todos)
        elif choice == "4":
            print("Goodbye!")
            break
        else:
            print("Invalid choice")

Now your to-do list persists — close the program, open again — your tasks are still there. That's the power of file handling.


Real World Example 2 — Simple Logger

Logging is recording what happens in your program over time:


    from datetime import datetime

    def log(message):
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log_entry = f"[{timestamp}] {message}\n"

        with open("app_log.txt", "a") as file:
            file.write(log_entry)

        print(log_entry.strip())


    log("Program started")
    log("User logged in: Gagan")
    log("User searched for: Python tutorials")
    log("User logged out")
    log("Program ended")

Output in terminal and saved to app_log.txt:

[2025-03-03 10:30:01] Program started
[2025-03-03 10:30:01] User logged in: Gagan
[2025-03-03 10:30:01] User searched for: Python tutorials
[2025-03-03 10:30:01] User logged out
[2025-03-03 10:30:01] Program ended

datetime is a built-in Python module — we'll cover modules in Stage 8. For now just know this is how you get current date and time.


Working with CSV Files

CSV (Comma Separated Values) is the most common file format for data. Excel files, database exports — almost everything can be saved as CSV.

A CSV file looks like this:

name,age,city,marks
Rahul,20,Delhi,85
Priya,21,Mumbai,92
Gagan,22,Delhi,78

Writing a CSV


    # Writing CSV manually
    students = [
        ["name", "age", "city", "marks"],    # header row
        ["Rahul", 20, "Delhi", 85],
        ["Priya", 21, "Mumbai", 92],
        ["Gagan", 22, "Delhi", 78]
    ]

    with open("students.csv", "w") as file:
        for row in students:
            line = ",".join(str(item) for item in row)
            file.write(line + "\n")

    print("CSV file created!")


Reading a CSV


    with open("students.csv", "r") as file:
        lines = file.readlines()

    # First line is header
    header = lines[0].strip().split(",")
    print(f"Columns: {header}")

    # Rest are data rows
    for line in lines[1:]:
        data = line.strip().split(",")
        print(f"Name: {data[0]}, Age: {data[1]}, City: {data[2]}, Marks: {data[3]}")

Output:

Columns: ['name', 'age', 'city', 'marks']
Name: Rahul, Age: 20, City: Delhi, Marks: 85
Name: Priya, Age: 21, City: Mumbai, Marks: 92
Name: Gagan, Age: 22, City: Delhi, Marks: 78

Python also has a built-in csv module that handles all edge cases — we'll see that in Stage 8.


File Paths

By default Python looks for files in the same folder as your Python script. But you can specify full paths:


    # Windows
    with open("C:\\Users\\Gagan\\Desktop\\notes.txt", "w") as file:
        file.write("Hello")

    # Mac/Linux
    with open("/home/gagan/Desktop/notes.txt", "w") as file:
        file.write("Hello")

    # Better way — relative path (subfolder)
    with open("data/notes.txt", "w") as file:    # saves in 'data' subfolder
        file.write("Hello")

For now just keep all your files in the same folder as your Python script — much simpler.


Quick Reference


    # Write (creates or overwrites)
    with open("file.txt", "w") as f:
        f.write("text\n")

    # Append (adds to existing)
    with open("file.txt", "a") as f:
        f.write("more text\n")

    # Read entire file
    with open("file.txt", "r") as f:
        content = f.read()

    # Read line by line
    with open("file.txt", "r") as f:
        for line in f:
            print(line.strip())

    # Read all lines into list
    with open("file.txt", "r") as f:
        lines = f.readlines()


Exercise 🏋️

Build a Personal Diary app with file persistence:

  1. When program starts — load existing diary entries from diary.txt
  2. Show a menu:
=== My Diary ===
1. Write new entry
2. Read all entries
3. Read today's entries
4. Exit
  1. Write entry — automatically add today's date and time before the entry, save to file
  2. Read all entries — display everything from the file
  3. Read today's entries — show only entries from today's date

Expected file format:

[2025-03-03 09:15:00] Today was a great day. I learned file handling in Python.
[2025-03-03 14:30:00] Just had lunch and back to coding!
[2025-03-04 10:00:00] New day, new learning!

Hint for today's date: datetime.now().strftime("%Y-%m-%d") Hint for checking today's entries: check if each line startswith today's date string wrapped in [


Error Handling in Python

Try / Except Why Error Handling? Run this program:     num = int ( input ( " Enter a number: " ))     print ( f "Double i...