←Back to Subject
Notes
Notes
Study material and explanations
Diagrams
Diagrams
Visualize concepts easily
Mnemonics
Mnemonics
Tricks to remember faster
One Night Prep
One Night Prep
Short and focused revision
Revision
Revision
Quick summary and key points
Important Questions
Important Questions
Practice most relevant questions
Notes

Exception Handling: try, except, finally, raise & Custom Exceptions

Study material and explanations for Unit 4

Exception Handling: Errors, try-except-finally, raise & Custom Exceptions

When a Python program encounters an unexpected situation during execution (like dividing a number by zero or opening a file that doesn’t exist), it crashes abruptly. Exception Handling allows us to catch these runtime errors gracefully so the program continues running smoothly.


1. Types of Errors in Python

Errors in a computer program are classified into three categories:

Error Type When It Occurs Description & Example
1. Syntax Errors (Compile-Time) Before execution (during parsing) Occurs when you violate Python grammar rules (e.g., missing colon : after if or unclosed bracket). Program won’t start until fixed.
2. Logical Errors During execution Program runs without crashing, but produces wrong output due to faulty formula (e.g., writing a - b instead of a + b).
3. Exceptions (Runtime Errors) During execution (at runtime) Syntax is 100% correct, but an invalid operation occurs at runtime (e.g., 10 / 0). Can be handled using try-except!

Common Built-in Exceptions in Python:

  • ZeroDivisionError: Dividing a number by 0 (10 / 0).
  • ValueError: Passing an invalid value to a function (int("abc")).
  • TypeError: Performing an operation on incompatible types ("Age: " + 20).
  • IndexError: Accessing a list index that is out of range (L[99]).
  • KeyError: Accessing a dictionary key that does not exist (d["missing"]).
  • FileNotFoundError: Trying to open a file that does not exist on disk.

2. Exception Handling Blocks: try, except, else, finally

Flow of try - except - else - finally in Python
1. try Block
Contains risky code that might raise an exception at runtime.
2. except Block
Catches and handles the exception if an error occurs inside try.
3. else Block (Optional)
Runs ONLY if NO exception was raised in the try block.
4. finally Block
Always executes (error or no error). Used to close files or DB connections!

Complete Example of try, except, else, and finally:

try:
    num1 = int(input("Enter numerator: "))
    num2 = int(input("Enter denominator: "))
    result = num1 / num2

except ZeroDivisionError:
    print("Error: You cannot divide a number by Zero!")

except ValueError:
    print("Error: Please enter valid integer numbers only!")

except Exception as e:
    print("Unexpected error occurred:", e)

else:
    # Runs only if no error occurred inside try
    print("Division successful! Result =", result)

finally:
    # Runs 100% of the time (used for cleanup)
    print("Execution of try-except block completed.")

3. Raising Exceptions Explicitly (raise Keyword)

Sometimes, you want to manually trigger (throw) an exception in your code when a business rule is violated. Python provides the raise keyword for this purpose.

def set_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative!")
    print("Age set to:", age)

try:
    set_age(-5)
except ValueError as err:
    print("Caught Exception:", err)

4. Need for Custom (User-Defined) Exceptions & Case Study

Why Do We Need Custom Exceptions?

Python’s built-in exceptions (ZeroDivisionError, ValueError, IndexError) only cover technical programming errors. However, real-world applications have domain-specific business rules, such as:

  • A bank ATM rejecting a withdrawal if the balance drops below Rs. 500 (InsufficientBalanceError).
  • An online exam portal rejecting registration if a student’s age is below 17 (UnderAgeCandidateError).

By creating Custom Exceptions (inheriting from Python’s built-in Exception class), our error messages become clear, self-explanatory, and easy to debug.


Case Study: ATM Banking Withdrawal with Custom Exception

Let’s create a user-defined exception class called InsufficientFundsError by inheriting from the built-in Exception class:

# Step 1: Define Custom Exception Class by inheriting from 'Exception'
class InsufficientFundsError(Exception):
    def __init__(self, message):
        super().__init__(message)

# Step 2: Use Custom Exception in a Real-World Banking Case Study
class BankATM:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            # Raise our custom exception!
            raise InsufficientFundsError(
                f"Withdrawal of Rs.{amount} failed! Available Balance is only Rs.{self.balance}."
            )
        self.balance -= amount
        print(f"Please collect your cash: Rs.{amount}")
        print(f"Remaining Balance: Rs.{self.balance}")

# Step 3: Test with try-except-finally
atm = BankATM(5000)

try:
    atm.withdraw(2000)   # Succeeds (Balance becomes 3000)
    atm.withdraw(4500)   # Triggers InsufficientFundsError!
except InsufficientFundsError as e:
    print("ATM Alert:", e)
finally:
    print("Thank you for banking with us! Please take your card.")

Output:

Please collect your cash: Rs.2000
Remaining Balance: Rs.3000
ATM Alert: Withdrawal of Rs.4500 failed! Available Balance is only Rs.3000.
Thank you for banking with us! Please take your card.