🐍
Syllabus / Python Programming / Phase 4: OOP Concepts, File Handling, Exception Handling (Days 16–20)
Intermediate

Exception Handling: try, except, finally, and Custom Exceptions

📂 Phase 4: OOP Concepts, File Handling, Exception Handling (Days 16–20) · Python Programming

An exception is an error detected during program execution that, left unhandled, crashes the program immediately. Exception handling lets you anticipate likely failure points and respond gracefully instead — keeping your program running, or at least failing in a controlled, informative way.

The Basic try-except Block

try:
    result = 10 / 0
except ZeroDivisionError:
    print("You can't divide by zero!")
# Output: You can't divide by zero!
# Without the try-except, this would crash the entire program

Catching Multiple Exception Types

try:
    num = int(input("Enter a number: "))
    result = 10 / num
except ValueError:
    print("That was not a valid number")
except ZeroDivisionError:
    print("You can't divide by zero!")

Catching Several Exceptions in One Block

try:
    value = int("abc")
except (ValueError, TypeError):
    print("Either a value error or a type error occurred")

Accessing the Exception Object

try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"An error occurred: {e}")
# An error occurred: division by zero

The else Clause — Runs Only If No Exception Occurred

try:
    num = int("42")
except ValueError:
    print("Conversion failed")
else:
    print(f"Conversion succeeded: {num}")   # only runs if try succeeded

The finally Clause — Always Runs

try:
    file = open("data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("File not found")
finally:
    print("This always runs, error or not")
    # commonly used for cleanup, like closing a file or releasing a resource

The Complete try-except-else-finally Structure

try:
    num = int(input("Enter a number: "))
    result = 100 / num
except ValueError:
    print("Please enter a valid integer")
except ZeroDivisionError:
    print("Cannot divide by zero")
else:
    print(f"Result: {result}")
finally:
    print("Execution complete")

Raising an Exception Manually

def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative")
    print(f"Age set to {age}")

try:
    set_age(-5)
except ValueError as e:
    print(e)   # Age cannot be negative

Creating a Custom Exception

For application-specific error conditions, defining your own exception class (inheriting from the built-in Exception) produces far more readable and meaningful error handling than reusing generic built-in exceptions everywhere.

class InsufficientBalanceError(Exception):
    """Raised when a withdrawal exceeds the available balance."""
    pass

class Account:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientBalanceError(
                f"Cannot withdraw {amount}; balance is only {self.balance}"
            )
        self.balance -= amount

acc = Account(500)
try:
    acc.withdraw(1000)
except InsufficientBalanceError as e:
    print(e)
# Cannot withdraw 1000; balance is only 500

Common Built-In Exception Types

ExceptionRaised When
ValueErrorA value has the right type but an invalid value (e.g. int("abc"))
TypeErrorAn operation is applied to an object of an inappropriate type
ZeroDivisionErrorDivision or modulus by zero
FileNotFoundErrorAttempting to open a file that does not exist
KeyErrorAccessing a dictionary key that does not exist
IndexErrorAccessing a list index that is out of range
Interview tip: "Why create a custom exception instead of just raising a generic Exception?" — a custom exception class lets calling code catch your specific error type precisely (except InsufficientBalanceError) without accidentally also catching unrelated errors that happen to also be generic Exceptions, making error handling far more intentional and readable.