Chapter 10 - Exceptions
1. Handling exceptions
1.1 Why We Need Error-Checking Code
Programs often deal with user input, files, networks, math operations, etc.
These can fail or behave unexpectedly.
Examples:
User types
"One-hundred fifty"instead of"150"Dividing by zero
File not found
Wrong data type
These errors are called exceptions.
1.2 What Is an Exception?
An exception is an unexpected error that occurs during program execution.
Example:
int("One-hundred fifty")This produces a ValueError, which normally crashes the program.
1.3 Goal of Exception Handling
Instead of crashing, the program should:
✔ Handle the error
✔ Show a helpful message
✔ Keep running normally
This is where try/except comes in.
1.4 The try / except Structure
Basic pattern:
try:
# code that might cause an exception
except:
# code to handle the exceptionHow it works:
Python executes everything inside the try block.
If no error occurs → skip the except block; continue normally.
If an exception happens:
Python jumps to the except block
Skips remaining statements inside the try block
Then continues running the rest of the program
1.5 Example Scenario (BMI Program)
User enters:
"One-hundred fifty"This causes:
int(user_input) # ValueErrorWithout a try/except, the entire program stops.
With try/except:
try:
weight = int(input("Enter weight: "))
except ValueError:
print("Error: Please enter a number.")Now the program handles the mistake gracefully.
1.6 Why Not Just Manually Check Everything?
You could write custom code like:
for c in user_input:
if not c.isdigit():
print("Invalid input!")BUT:
It complicates the code
It misses many real-world error cases
Try/except is cleaner, shorter, and safer
1.7 Key Points About try Execution Flow
If no exception:
All code in
tryexecutesexceptis skipped
If an exception occurs:
Python jumps immediately to
exceptRemaining statements in
tryare not executed
Example:
try:
a = 5
b = int("hello") # exception occurs here
c = 10 # never executed
except:
print("Bad input!")Quick Cheatsheet
Use try/except when:
Converting user input
Opening a file
Doing math that might fail
Working with networks or APIs
Syntax:
try:
risky_code()
except SomeError:
handle_issue()Behavior:
Prevents program crashes
Keeps code clean
Lets you recover from bad input or unexpected events
2. Multiple handlers
2.1 Why Handle Multiple Exception Types?
Different lines of code may cause different types of errors.
Example:
int("abc")→ ValueErrordividing by zero → ZeroDivisionError
using an unknown variable → NameError
adding incompatible types → TypeError
A single try block may need to deal with more than one possible error.
2.2 Multiple except Blocks
Python allows multiple exception handlers, one for each type of error:
try:
# risky code
except ValueError:
print("Bad number!")
except ZeroDivisionError:
print("Cannot divide by zero!")How it works:
Python executes only the first matching except block.
Other handlers are ignored after that.
2.3 Catchall except: — Use With Caution
A catchall handler:
except:
print("Something went wrong")will catch any error, even unexpected ones.
Good practice:
Avoid using bare
except:unless absolutely necessary.It can hide bugs, making debugging harder.
2.4 What Happens If an Exception Isn't Handled?
If Python encounters an exception with no matching except block:
It becomes an unhandled exception
The program prints a traceback
The program halts
Example:
Traceback (most recent call last):
...
ZeroDivisionError: division by zero2.5 Handling Multiple Exception Types in One Block
Sometimes multiple exception types should produce the same response.
Use a tuple:
except (ValueError, TypeError):
print("Invalid input!")This block handles either ValueError or TypeError.
2.6 BMI Example (Expanded)
If the user enters:
"abc"→ ValueError"0"for height → ZeroDivisionError
A program can handle both:
try:
bmi = weight / (height * height)
except ValueError:
print("You must enter numbers.")
except ZeroDivisionError:
print("Height cannot be zero.")Quick Cheatsheet
Multiple handlers:
try:
...
except ValueError:
...
except TypeError:
...
Catch multiple types:
except (ValueError, TypeError):
Avoid catchall:
except:
# not recommended
Unhandled exception:
Program stops and prints an error.
3. Raising exceptions
3.1 The Problem With Naive Error Checking
A beginner might add if-else checks everywhere:
if weight < 0:
print("Invalid weight.")
else:
if height < 0:
print("Invalid height.")
else:
bmi = weight / (height * height)
print(bmi)Why this is bad:
Normal program flow becomes cluttered
You can no longer clearly see:
get weight → get height → compute BMIDuplicate checks
You might check for negative values multiple times.Easy to introduce inconsistencies
Ex: using<in one place but<=in another.
This leads to messy, fragile code.
3.2 Cleaner Design: Use Exceptions + raise
Instead of mixing error checks into the normal code, you:
Put all normal code in a try block.
If something is wrong, use raise to trigger an exception.
Handle errors neatly in except blocks.
3.3 How raise Works
A raise statement creates and throws an exception immediately, exiting the try block.
Example:
raise ValueError("Invalid weight.")This creates a ValueError object with a helpful message, and jumps to the matching except block.
3.4 Improved BMI Program Using Exceptions
try:
weight = int(input("Enter weight in pounds: "))
if weight <= 0:
raise ValueError("Invalid weight.")
height = int(input("Enter height in inches: "))
if height <= 0:
raise ValueError("Invalid height.")
bmi = weight * 703 / (height * height)
print(f"BMI: {bmi}")
except ValueError as excpt:
print(excpt)Why this is better:
✔ Normal flow is clear:get weight → get height → compute BMI
✔ Error handling is separated
All validation happens inside simple checks, but the reaction to errors is grouped in one place.
✔ Messages are stored in the exception
The as excpt part binds the error to a variable so you can print the message:
print(excpt)3.5 Why ValueError?
Python exceptions communicate what kind of mistake occurred.
ValueError= the input value is wrongTypeError= value type is wrongNameError= something doesn’t existetc.
Using the correct exception type makes your code:
more readable
easier to debug
easier to integrate with other systems
3.6 Key Ideas to Remember
✔ Raising an exception:
Immediately stops the try block
Sends control to the except block
Allows you to attach a custom message
✔ Using exceptions keeps your code clean:
No messy nested if-else chains
Valid logic stays visible and readable
Error handling is grouped and consistent
✔ Use as excpt when you want access to the error message.
3.7 What printing the exception shows
If you write:
raise ValueError("Invalid height.")
and the except block says:
except ValueError as excpt:
print(excpt)
the output will be:
Invalid height.
Because the exception object stores the message you passed in.
4. Exceptions with function
4.1 Key Idea: Exceptions Travel Up the Call Stack
If code inside a function raises an exception and that function does not handle it, Python:
Immediately exits the function
Goes back to the caller to look for a matching
exceptblockIf not found, it goes further up the call chain
If still not found → unhandled exception → program stops
This automatic climbing up the call hierarchy is what makes exceptions powerful.
4.2 Why This Is Useful
It keeps normal logic clean.
Example program (conceptually):
def get_weight():
w = int(input("Enter weight: "))
if w <= 0:
raise ValueError("Invalid weight.")
return w
def get_height():
h = int(input("Enter height: "))
if h <= 0:
raise ValueError("Invalid height.")
return h
try:
weight = get_weight() # if ValueError → jumps to except
height = get_height() # if ValueError → jumps to except
print(weight * 703 / (height * height))
except ValueError as excpt:
print(excpt)What happens if the user enters “-40” for weight?
get_weight()checks, finds an error, runs:raise ValueError("Invalid weight.")The exception is not handled inside
get_weight(), so Python:Immediately leaves
get_weight()Returns to the caller (
tryblock in main script)Finds
except ValueErrorExecutes the handler
4.3 Why This Is Cleaner Than Returning Special Values
Without exceptions, you'd need something like:
w = get_weight()
if w == -1:
print("Invalid weight.")
else:
h = get_height()
if h == -1:
print("Invalid height.")
else:
print("BMI:", ...)Problems:
Lots of nested if-else statements
Harder to read
Easy to introduce mistakes
Violates clean separation between normal flow and error handling
Exceptions solve this:
Normal flow is simple:
get weight → get height → compute BMIError-handling code is centralized in one place
No need to return fake values like -1 or None
4.4 Exception Propagation = Better Structure
How it flows:
get_weight()
↓ raises ValueError
(no handler here)
↓
main script try block
↓ finds matching except ValueError
executes except blockThis is called exception propagation.
4.5 Benefits of Using Exceptions in Functions
✔ Cleaner function design
Functions focus on what they do, not how to handle bad input.
✔ Fewer bugs
No need for sentinel return values like -1 or None.
✔ Improves readability
Error-handling is separated from main logic.
✔ Reusable functions
get_weight() does not need to know how errors will be handled — any caller can handle it.
Core Takeaways
A raised exception immediately exits the function.
If not handled inside the function, Python looks for a handler in the caller.
This continues up the chain until a matching handler is found.
This makes code clean, readable, and modular.
Without exceptions, you'd need messy branching logic.
5. Using finally to clean up
5.1 Purpose of finally
The finally block contains code that must run no matter what happens — whether:
the
tryblock succeeds,a handled exception occurs,
an unhandled exception occurs,
or the
tryblock exits early (viareturn,break, orcontinue).
➤ Main use case:
Performing cleanup actions, such as:
closing files
releasing resources
cleaning temporary data
5.2 Execution Rules for finally
✔ If NO exception occurs:
tryblock runs successfullyfinallyblock runs nextprogram continues normally
✔ If a HANDLED exception occurs:
tryblock runs until errorcorrect
exceptblock runsfinallyblock runs afterward
✔ If an UNHANDLED exception occurs:
tryruns until errorno matching
exceptfinallystill runsthen the exception is re-raised
✔ If the try block is exited early:
finally still runs even if try contains:
returnbreakcontinue
5.3 Structure With finally
try:
# risky code
except SomeError:
# handle specific error
finally:
# always runs (cleanup code)finally must always appear last in a try/except structure.
5.4 Example Use Case: File Handling
A program tries to read integers from a file.
Even if errors occur (file missing, invalid data), finally ensures the file is closed.
try:
f = open("data.txt")
for line in f:
num = int(line) # may raise ValueError
except Exception as e:
print("Error:", e)
finally:
f.close() # ALWAYS executesKey Takeaways
finallyalways executes — success, failure, or interruption.Ideal for cleanup actions.
Works with or without specific
exceptblocks.Prevents resource leaks (like open files).
Must be the last clause in the try structure.
6. Custom exception types
6.1 Raising Built-In Exceptions
Python provides many built-in exception types that you can raise directly.
Example:
if my_num < 0:
raise ValueError("my_num < 0")Use built-in exception types whenever they clearly describe the error:
ValueErrorTypeErrorZeroDivisionErrorKeyErroretc.
6.2 Why Create Custom Exceptions?
Custom exception types are useful when:
You want to represent program-specific errors.
Built-in exceptions don't clearly describe the situation.
You want exception handlers to catch only your custom, meaningful errors.
You’re writing larger modules/libraries.
6.3 How to Define a Custom Exception
A custom exception is just a class that inherits from Python’s built-in Exception class.
Example:
class LessThanZeroError(Exception):
def __init__(self, value):
self.value = valueOr a minimal version:
class LessThanZeroError(Exception):
pass➡ A class must contain at least one statement (pass works if nothing else is needed).
6.4 Raising a Custom Exception
Just like built-ins, but using your new type:
if my_num < 0:
raise LessThanZeroError("Number must not be negative")6.5 Naming Convention
Good practice:
👉 Custom exception names should end with "Error"
Examples:
LessThanZeroErrorFileFormatErrorInvalidMoveError
This matches Python's naming style and makes the purpose obvious.
6.6 Handling a Custom Exception
Handled exactly like built-in exceptions:
try:
do_something()
except LessThanZeroError as e:
print("Error:", e)6.7 How Much Code Should a Custom Exception Class Have?
Usually very little.
Custom exceptions are typically simple and only store:
a message
any needed data
Why?
Because exceptions aren't used like normal classes; they only need to carry information to the handler.
Key Takeaways
Use built-in exceptions when appropriate.
Create custom exceptions for application-specific error types.
Custom exceptions inherit from
Exception.Keep custom exception classes simple.
Name them ending with
"Error"for clarity.