Exception Handling
Handling runtime errors gracefully with try/except/else/finally, catching multiple exception types, raising exceptions deliberately with raise, and defining custom exception classes.
No matter how carefully you write code, things go wrong at runtime — a user enters text instead of a number, a file doesn't exist, the internet connection drops. Exception handling is how Python lets your program deal with these problems gracefully, instead of crashing.
1. What are Errors and Exceptions?
What is it?
- An error is a general term for anything that goes wrong in a program.
- An exception is a specific type of error that occurs during program execution (at runtime) — Python detects it and, if unhandled, stops the program and shows a traceback message.
Definition: An exception is an event that disrupts the normal flow of a program's execution, usually caused by an invalid operation.
Why do we use exception handling?
Without it, a single unexpected input (like text where a number was expected) would crash the entire program. Exception handling lets you anticipate possible problems and respond to them sensibly — showing a friendly message, retrying, or logging the issue — instead of the whole program stopping abruptly.
Simple Example — An Unhandled Exception
pythonage = int(input("Enter your age: ")) print(f"You are {age} years old")
If the user types "abc" instead of a number:
ValueError: invalid literal for int() with base 10: 'abc'The program crashes completely at this point — nothing after this line runs.
Common Built-in Exception Types
| Exception | When it Occurs |
|---|---|
ValueError | Invalid value for an operation (e.g., int("abc")) |
TypeError | Wrong data type used in an operation |
ZeroDivisionError | Dividing by zero |
FileNotFoundError | Trying to open a file that doesn't exist |
KeyError | Accessing a dictionary key that doesn't exist |
IndexError | Accessing a list index that's out of range |
NameError | Using a variable that hasn't been defined |
AttributeError | Calling a method/attribute that doesn't exist on an object |
2. try and except
What is it?
try lets you "attempt" a risky piece of code. If an exception occurs, Python jumps to the matching except block instead of crashing the whole program.
Syntax
pythontry: # risky code except ExceptionType: # code that runs if that exception occurs
Simple Example
pythontry: age = int(input("Enter your age: ")) print(f"You are {age} years old") except ValueError: print("That's not a valid number. Please enter digits only.")
Sample Interaction:
Enter your age: abc
That's not a valid number. Please enter digits only.Explanation of the Code
- Python attempts everything inside
try. - If a
ValueErroroccurs (like converting"abc"toint), Python immediately jumps to the matchingexcept ValueError:block instead of crashing. - The program continues running normally after the
exceptblock — it doesn't stop.
Handling Multiple Exception Types
pythontry: num1 = int(input("Enter first number: ")) num2 = int(input("Enter second number: ")) print(num1 / num2) except ValueError: print("Please enter valid numbers.") except ZeroDivisionError: print("You cannot divide by zero.")
Catching Any Exception (Use Sparingly)
pythontry: risky_operation() except Exception as e: print("Something went wrong:", e)
Explanation: except Exception as e catches almost any error and stores its details in e. This is useful as a safety net, but catching specific exceptions (like ValueError, ZeroDivisionError) is better practice, since it doesn't accidentally hide unrelated bugs.
Real-World Example
A banking app should never crash just because a user typed letters into an amount field — it should catch the error and ask them to re-enter a valid number.
Common Mistakes
- Using a bare
except:(with no exception type at all), which catches literally everything — including mistakes you'd actually want to notice, like typos in your own code. Always specify the exception type when possible. - Wrapping too much code inside one
tryblock, making it unclear exactly which line caused the problem.
Important Points
try/exceptprevents a single error from crashing the entire program.- You can have multiple
exceptblocks for different exception types. - Catching specific exceptions is better practice than catching everything blindly.
Practice
- Write a program that asks the user for two numbers and divides them, handling both
ValueErrorandZeroDivisionError.
3. else with try
What is it?
An optional else block that runs only if no exception occurred in the try block.
Syntax
pythontry: # risky code except ExceptionType: # runs if an exception occurred else: # runs only if NO exception occurred
Simple Example
pythontry: num = int(input("Enter a number: ")) except ValueError: print("Invalid input.") else: print(f"You entered {num}, and it's a valid number!")
Explanation
- The
elseblock is a clean way to separate "the risky part" (insidetry) from "what happens next, only on success" (insideelse) — keeping the code that could fail isolated from the code that depends on it succeeding.
Important Points
elseonly runs if thetryblock completes with no exception.- Helps keep code organized: risky logic in
try, success-path logic inelse.
4. finally
What is it?
A finally block always runs — whether an exception occurred or not, and even if the exception wasn't caught at all. It's typically used for cleanup actions, like closing a file or a database connection.
Syntax
pythontry: # risky code except ExceptionType: # handle exception finally: # this ALWAYS runs, no matter what
Simple Example
pythontry: file = open("data.txt", "r") content = file.read() print(content) except FileNotFoundError: print("File not found.") finally: print("Attempted to read the file — cleanup complete.")
Explanation
- Whether the file is found or not, the
finallyblock always executes — this is whyfinallyis the classic place to close files or database connections, ensuring cleanup happens no matter what. - (In modern Python,
with open(...)handles file closing automatically, butfinallyis still essential for other types of cleanup, like closing network connections.)
Common Mistakes
- Assuming
finallyonly runs on success — it actually runs in every case, including when an exception is raised and not even caught.
Important Points
finallyalways executes — success, failure, or even an uncaught exception.- Commonly used for guaranteed cleanup: closing files, releasing resources, disconnecting from a database.
Comparison Table — try / except / else / finally
| Block | Runs When |
|---|---|
try | Always attempted first |
except | Only if a matching exception occurs |
else | Only if NO exception occurred |
finally | ALWAYS — regardless of success or failure |
5. Raising Exceptions with raise
What is it?
raise lets you deliberately trigger an exception yourself — useful when your own code detects an invalid situation that should stop normal execution.
Simple Example
pythondef withdraw(balance, amount): if amount > balance: raise ValueError("Insufficient balance for this withdrawal") return balance - amount try: new_balance = withdraw(1000, 1500) except ValueError as e: print("Error:", e)
Output:
Error: Insufficient balance for this withdrawalExplanation of the Code
- Instead of letting the program continue with an invalid state (a negative balance),
raisedeliberately stops execution of thewithdraw()function and hands control to the nearest matchingexcept. - The custom message
"Insufficient balance for this withdrawal"explains exactly what went wrong, which is far more helpful than a generic crash.
Real-World Example
A form validation function might raise a ValueError if a required field is empty, or if an age entered is negative — deliberately flagging a problem the program itself knows to check for.
Important Points
raisecan be used with any built-in exception type, or a custom one (see below).- Raising exceptions with clear messages makes debugging far easier.
Practice
- Write a function
set_age(age)that raises aValueErrorif age is negative, and test it with atry/except.
6. Custom Exceptions
What is it?
You can define your own exception types by creating a class that inherits from Python's built-in Exception class — useful when built-in exceptions don't clearly describe your specific problem.
Simple Example
pythonclass InsufficientBalanceError(Exception): pass def withdraw(balance, amount): if amount > balance: raise InsufficientBalanceError("Withdrawal amount exceeds available balance") return balance - amount try: withdraw(1000, 1500) except InsufficientBalanceError as e: print("Transaction failed:", e)
Output:
Transaction failed: Withdrawal amount exceeds available balanceExplanation of the Code
class InsufficientBalanceError(Exception):creates a brand-new exception type, specific to this application's needs.passmeans the class doesn't need any extra code — it inherits everything it needs fromException.- This custom exception can now be raised and caught just like any built-in one, but its name makes the actual problem immediately clear to anyone reading the code.
Real-World Example
Large applications commonly define custom exceptions like InvalidLoginError, OutOfStockError, or PaymentDeclinedError — these communicate the exact business problem far more clearly than a generic ValueError would.
Common Mistakes
- Forgetting to inherit from
Exception(or one of its subclasses) when creating a custom exception class. - Creating overly specific custom exceptions for situations a built-in exception already covers well.
Important Points
- Custom exceptions must inherit from
Exception(directly or indirectly). - They make error handling far more readable and meaningful in larger applications.
Practice
- Create a custom exception
NegativeValueErrorand use it in a function that calculates the square root of a number, raising the exception if the number is negative.
Common Beginner Mistakes — Summary for This Section
- Using a bare
except:that catches everything, hiding real bugs. - Wrapping too much code in one
tryblock, making it hard to know what actually failed. - Forgetting that
finallyalways runs, even after an exception. - Not providing a clear message when raising an exception.
Cheat Sheet — Exception Handling
pythontry: risky_code() except ValueError: handle_value_error() except (TypeError, KeyError) as e: handle_multiple_types(e) except Exception as e: handle_anything_else(e) else: only_runs_if_no_exception() finally: always_runs() raise ValueError("custom message") class MyCustomError(Exception): pass
Interview Questions
Q1. What is the difference between an error and an exception? Answer: "Error" is a general term for anything going wrong. An exception is specifically a runtime event Python detects and can be caught and handled using try/except, preventing a crash.
Q2. What is the purpose of the `finally` block? Answer: Code inside finally always runs, regardless of whether an exception occurred or was handled — typically used for guaranteed cleanup actions like closing files or connections.
Q3. When does the `else` block (paired with `try`) execute? Answer: Only when the try block completes successfully with no exception raised.
Q4. Why is using a bare `except:` considered bad practice? Answer: It catches every possible exception, including ones you didn't anticipate, which can hide genuine bugs and make debugging much harder. Catching specific exception types is safer and clearer.
Q5. How do you create a custom exception in Python? Answer: By defining a class that inherits from Exception (or one of its subclasses), then using raise to trigger it when needed.
Q6. What exception is raised when dividing a number by zero? Answer: ZeroDivisionError.
Practice Questions
Beginner
- Write a program that asks for a number and handles the case where the user enters non-numeric text.
- Write a program that divides two numbers and handles division by zero.
- Write a program that tries to open a file that may not exist, and prints a friendly message if it isn't found.
- Write a program that accesses a dictionary key that might not exist, using
try/exceptinstead of.get(). - Write a program with a
finallyblock that always prints "Program finished" no matter what.
Intermediate
- Write a function that raises a
ValueErrorif a given age is negative, and handle it in atry/exceptblock. - Write a program that handles multiple exception types (
ValueError,ZeroDivisionError,IndexError) separately with different messages. - Create a custom exception
InvalidAgeErrorand use it to validate a user's age input. - Write a program that keeps asking the user for a valid number until they provide one, using a loop combined with
try/except. - Write a function that reads a file and raises a custom
EmptyFileErrorif the file exists but is empty.
Challenge
- Build a simple calculator that handles invalid input, division by zero, and invalid operators, each with a clear, specific error message.
- Create a custom exception hierarchy: a base
AppErrorexception, with two subclassesValidationErrorandAuthenticationError, and demonstrate catching each specifically as well as catching the base type generically. - Write a program that validates a list of user-submitted ages, collecting all invalid ones into an error report instead of stopping at the first invalid value.