# Exception Handling: Try, Except, Finally

> Chapter 7 of the Courseiva PYTHON-PCAP curriculum — https://courseiva.com/learn/python-pcap/exception-handling-fundamentals

**Official objective:** 3.1 — Implement try-except blocks, handle multiple exceptions, and use finally for cleanup.

## Introduction

What happens when your Python code hits an error it did not expect, and the whole programme crashes instead of handling it gracefully? Exception handling is the safety net that lets you catch those errors, decide what to do about them, and keep your programme running, which is exactly what the PCAP-31-03 exam tests you on with the try, except, and finally blocks.

## The Commercial Kitchen Ticket Analogy

A commercial kitchen ticket is both a promise and a potential disaster. The head chef reads an order for a steak, medium-rare, with mashed potatoes and a side of asparagus. This ticket is the try block. The chef directs the line cooks to execute each step: sear the steak, mash the potatoes, blanch the asparagus. Everything can go perfectly, and the plate goes to the waiter. But the kitchen knows things go wrong. A steak can be dropped on the floor. The potato masher can break. The asparagus can burn. Each of those failures is a specific exception. The chef has a laminated card taped to the pass: 'Steak dropped: fire a new one immediately.' 'Masher broken: switch to the backup hand masher.' 'Burnt asparagus: use the prepped broccoli from the walk-in.' Those instructions are the except blocks. Each disaster has its own handler. But no matter what happens, at the end of every shift, every single ticket must be filed. The dishwashers clean the pans, the prep cooks restock the station, and the manager locks the walk-in. The chef never skips closing duties, even if a ticket was perfect or the kitchen caught fire. That final ritual is the finally block: the cleanup that runs regardless of success or failure. The ticket is the operation; the disasters are the exceptions; the closing procedure is the guarantee that the kitchen is ready for the next service.

## Core explanation

A Python programme executes statements one by one, top to bottom. When it encounters an instruction it cannot complete, it raises an exception. An exception is an event that signals something has gone wrong, like dividing by zero, trying to access a file that does not exist, or asking for an index that is out of range in a list. If the programme does not catch that exception, it terminates immediately and prints a traceback to the console. That is a crash. Exception handling lets you intercept that event and respond to it, rather than letting the programme die.

In Python, you use the try statement to wrap code that might cause an exception. The try block tells Python: 'Watch this code very carefully. If something goes wrong, I have a plan.' After the try block, you write one or more except blocks. An except block contains code that runs only if a specific exception type is raised inside the try block. For example, if you are trying to convert user input to an integer, you can catch a ValueError when the user types letters instead of digits.

The syntax looks like this:
- try:
-     number = int(input("Enter a number: "))
- except ValueError:
-     print("That was not a valid number.")

If the user enters 'hello', the int() function raises a ValueError. The try block stops executing immediately, and Python jumps to the except ValueError block. The print statement runs, and then the programme continues with the next line after the except block. It does not crash. This is called catching an exception.

You can handle multiple exception types by writing multiple except blocks. Python checks each except block in order and runs the first one whose exception type matches the raised exception. If none match, the exception propagates up and the programme crashes, unless a broader except clause catches it. A bare except block, with no exception type, catches everything. This is dangerous because it can hide bugs, but it exists. The PCAP exam expects you to know this hierarchy.

After all the try and except blocks, you can optionally add a finally block. The finally block always executes, whether an exception was raised or not, and even if a return, break, or continue statement is used inside the try or except blocks. The finally block is the place for cleanup actions that must happen regardless, such as closing a file, releasing a network socket, or releasing a lock. Python guarantees the finally block runs before the try-except block exits, making it a reliable tool for resource management.

A common pattern is try-except-else-finally. The else block runs only if no exception was raised in the try block. It is useful for code that should execute only on success, separate from the code that handles failures. For example:
- try:
-     file = open("data.txt", "r")
- except FileNotFoundError:
-     print("File not found.")
- else:
-     content = file.read()
-     print(content)
- finally:
-     file.close()

Here, the else block ensures you only read the file if it was successfully opened. The finally block ensures the file is closed, even if the read operation itself raises an exception. This structure replaces older, error-prone patterns where you had to check return codes or use nested if statements to manage resources. Pythonic exception handling is cleaner, more readable, and more reliable than checking return values manually.

Key terms you must know: raise, exception, try, except, else, finally, and traceback. A traceback is the report Python prints when an unhandled exception crashes the programme. It shows the call stack and the line that caused the error. Understanding tracebacks helps you debug, but the PCAP exam tests your ability to prevent them with proper exception handling.

Python has many built-in exception types. The most common ones for PCAP include ZeroDivisionError, ValueError, TypeError, IndexError, KeyError, FileNotFoundError, and AttributeError. You can also define custom exceptions by creating a class that inherits from Exception. The exam expects you to recognise these types and write handlers for them correctly.

## Real-world context

An IT professional writes a script to process invoice data from a CSV file uploaded by a business user. The script must read the file, parse each row, and insert the data into a database. Several things can go wrong: the file might be missing, the file might have invalid data, the database might be unreachable, or a row might contain a value that violates a database constraint. Without exception handling, any single failure stops the entire batch job, and the user receives an unhelpful crash message. With exception handling, the script can handle each failure gracefully and report exactly which rows failed and why.

Step by step, the IT professional builds this:
- They wrap the file open operation in a try block.
- They catch FileNotFoundError to inform the user the file path is wrong.
- They catch PermissionError if the script lacks read access.
- Inside the file reading loop, they wrap the parsing of each row in another try block.
- They catch ValueError for rows where a numeric field contains text, and they log the row number and the bad value to a separate error log file.
- They continue to the next row instead of failing the whole script.
- When inserting into the database, they catch IntegrityError (a database-specific exception) for duplicate key violations and log a human-readable message.
- They use a finally block to close the database connection and the file handle, ensuring no resources leak even if an exception occurs mid-insert.

In a production environment, this pattern is critical. A company processing thousands of invoices daily cannot afford a single bad row to take down the entire pipeline. Exception handling turns an unrecoverable crash into a logged, manageable incident. The IT professional also writes a separate monitoring script that alerts them if the error log grows beyond a threshold, because exceptions that are caught silently can mask bigger problems. This is why the exam focuses on the finally block for cleanup: forgotten open files or database connections are a classic source of production outages.

The IT professional also uses custom exceptions in larger codebases. For example, they define a class called InvalidInvoiceRowError that carries extra data about which field failed. Then they can write an except block that catches only that custom type, prints a detailed message for the user, and logs the full context for debugging. This keeps the code organised and the error handling specific.

## Exam focus

The PCAP-31-03 exam tests your ability to write and interpret try-except-finally blocks in multiple choice and code reading questions. Expect questions that present a snippet of code with a try block and ask what the output will be, or whether an exception will be caught. They love to trap you with the following patterns:

- The else block executes only if no exception occurs. A common trick question shows an else block that tries to use a variable defined only inside the try block after a successful operation. The variable exists because the try block completed, but candidates sometimes think it does not.
- The finally block always executes, even if there is a return statement in the try or except block. This is a classic exam trap. If a function has a try block with a return and a finally block, the finally runs before the function actually returns. The return value is determined, but the finally code still executes. Questions ask what value is returned, and the answer is the value from the return statement, but the finally might print something or modify a mutable object before the function exits.
- Multiple except blocks are checked in order. The first match wins. If you put a generic except Exception before a specific except ValueError, the specific block will never run. Exam questions ask which except block catches a given exception type, and the answer depends on the order.
- A bare except block traps all exceptions, including KeyboardInterrupt and SystemExit, which are usually undesirable. The exam tests whether you recognise this and understand why it is bad practise.
- The raise statement without an argument inside an except block re-raises the same exception. This is called re-raising, and it is used when you want to log the error but let it propagate. The exam asks what happens after a bare raise inside an except handler.

Key definitions to memorise:
- Exception: an event that disrupts normal programme flow.
- Traceback: the report of the call stack when an exception is not handled.
- try: the block that contains code to monitor for exceptions.
- except: the block that handles a specific exception type.
- else: the block that runs if no exception occurred in the try.
- finally: the block that always runs, used for cleanup.
- raise: the statement that triggers an exception manually.

Common exam question formats:
- 'What is the output of this code?' with a try-except-finally block containing a divide by zero.
- 'Which exception is raised by this line?' testing recognition of ValueError, TypeError, etc.
- 'Which block always executes?' testing the finally guarantee.
- 'What is wrong with this exception handling code?' where the trap is an overly broad except or missing finally.

Study tip: memorise the order of execution: try -> first matching except (if exception) or else (if no exception) -> finally. Practise tracing code on paper without running it, because that is exactly what the exam requires.

## Step by step

1. **Identify the risky code** — Look at your programme and find statements that could fail, such as user input conversion, file operations, network calls, or division by zero. These are the candidates to wrap in a try block.
2. **Wrap the risky code in a try block** — Place the statements that might raise exceptions inside a try block. Python will watch this block and stop executing it if an exception occurs, then jump to the exception handler.
3. **Write specific except blocks** — For each exception type you anticipate, write an except block that handles it appropriately. Name the exception type exactly (e.g., except ValueError:). Order them from most specific to most generic.
4. **Add an optional else block** — If you have code that should run only when the try block succeeds without any exception, put it in the else block. This keeps the success path separate from the error handling, making the code clearer.
5. **Add a finally block for cleanup** — Place resource release code, like closing files or database connections, in the finally block. This code runs no matter what, ensuring you never leave resources open, even if an exception happens.
6. **Test with deliberately faulty input** — Run your code with inputs that trigger each exception type to verify that your handlers work correctly. Confirm that the finally block always prints or executes its cleanup code, even when you force an early exit with return.

## Comparisons

### else block vs finally block

**else block:**
- Runs only if no exception occurred in the try block.
- Ideal for code that depends on successful execution of the try block.
- Skipped entirely if an exception is raised and caught.

**finally block:**
- Always runs, regardless of whether an exception occurred.
- Used for cleanup actions like closing files or releasing resources.
- Executes even if there is a return, break, or continue in try or except.

### Specific except (e.g., except ValueError) vs Bare except (except:)

**Specific except (e.g., except ValueError):**
- Catches only one named exception type.
- Safer because it does not hide unexpected errors.
- Allows different handlers for different error types.

**Bare except (except:):**
- Catches every exception, including SystemExit and KeyboardInterrupt.
- Considered bad practise in most production code because it masks bugs.
- Should only be used if you re-raise the exception after logging it.

### try-except vs if-else with error checking

**try-except:**
- Separates normal flow from error handling cleanly.
- Does not require checking return values or error codes manually.
- More readable and reduces nesting in code.

**if-else with error checking:**
- Requires explicit checks for every possible failure condition.
- Can lead to deeply nested if statements and harder-to-read code.
- More error-prone because a missed check can cause a crash.

### Raise an exception (raise) vs Print an error message

**Raise an exception (raise):**
- Signals an error condition that can be caught and handled higher up.
- Preserves the call stack for debugging.
- Does not pollute output with messages meant for the developer.

**Print an error message:**
- Only prints a message and continues execution.
- Does not allow calling code to react to the error programmatically.
- Easily missed in logs or console output, leading to silent failures.

## Diagram

_Flowchart showing the execution path through a try-except-else-finally block, including what happens when an exception is raised and whether it is caught or propagates._

```mermaid
flowchart TD
    A[Start: Code Executes] --> B{Inside a try block?}
    B -- Yes --> C[Execute try block code]
    C --> D{Exception raised?}
    D -- No --> E[Execute else block if present]
    E --> F[Execute finally block]
    F --> G[Continue programme]
    D -- Yes --> H[Stop try block execution]
    H --> I{Does any except block match?}
    I -- Yes --> J[Run matching except block]
    J --> F
    I -- No --> K[Exception propagates up the call stack]
    K --> L{Is there a higher-level handler?}
    L -- Yes --> M[Handle at higher level]
    M --> F
    L -- No --> N[Crashes: Traceback printed]
    N --> O[Programme exits]
```

## Common misconceptions

- **Misconception:** The finally block only runs if an exception occurs. **Reality:** The finally block always runs, regardless of whether an exception was raised or caught. (Beginners see 'finally' as a response to errors, but it is really a guaranteed cleanup step. Real-life analogies like 'always wash your hands regardless of outcome' help, but many still confuse it with an else block.)
- **Misconception:** If you have a return statement in the try block, the finally block will not run. **Reality:** The finally block runs even if the try or except block contains a return, break, or continue statement. The function does not return until after the finally completes. (Most languages do not have this guarantee, so programmers coming from other backgrounds assume return is immediate. Python's finally is stricter.)
- **Misconception:** Catching Exception with a bare 'except:' is good practise because it prevents all crashes. **Reality:** Bare except catches all exceptions including SystemExit and KeyboardInterrupt, which should usually not be caught. It hides bugs and is considered bad practise except in very specific situations like cleanup code that must run regardless. (New developers want to prevent any crash at any cost, and a bare except seems like an easy solution. They do not know that some exceptions are meant to terminate the programme.)
- **Misconception:** The except block can access variables that were created in the try block only if the try block succeeded completely. **Reality:** If the try block raised an exception partway through, variables defined before the exception point are still accessible in the except block. The try block's scope is the same as the surrounding function. (Beginners think an exception 'undoes' everything in the try block, but only the remaining lines of the try block are skipped. The namespace is unaffected.)
- **Misconception:** You can only have one except block per try block. **Reality:** You can have multiple except blocks, each handling a different exception type. Python checks them in order and executes the first match. (Beginners are used to simple examples with one except, but real-world code requires handling several distinct error types separately.)

## Key takeaways

- The try block contains code that might raise an exception, and execution jumps to the first matching except block when an exception occurs.
- The finally block always executes, even if the try or except block contains a return, break, or continue statement.
- Multiple except blocks are checked in order, so place specific exception types before generic ones to ensure they are caught.
- The else block runs only if no exception was raised in the try block, making it ideal for success-only logic.
- A bare except: clause catches all exceptions, including SystemExit and KeyboardInterrupt, which is usually a mistake in production code.
- You can re-raise an exception inside an except block using a bare raise statement, which preserves the original traceback.
- Custom exceptions are created by subclassing Exception and help organise error handling in large applications.
- Python guarantees that the finally block executes before the try-except block exits, making it reliable for releasing resources like files and network connections.

## FAQ

**What is the difference between else and finally in exception handling?**

The else block runs only if no exception was raised in the try block. The finally block always runs, whether an exception occurred or not, and is used for cleanup like closing files.

**Can I have multiple except blocks for the same try block?**

Yes, you can have many except blocks. Python checks them in order and runs the first one whose exception type matches the raised exception. This lets you handle different error types separately.

**Does the finally block run if there is a return statement in the try block?**

Yes. The finally block executes before the function actually returns, even if the try or except block contains a return, break, or continue. The return value is determined first, then the finally runs, then the function returns.

**What happens if I catch an exception but do not handle it properly?**

If you catch an exception and do not re-raise it or take corrective action, the programme continues silently. This can hide bugs because the error is swallowed. Good practise is to log the error or re-raise it if you cannot handle it fully.

**What is a bare except and why is it bad?**

A bare except is an except clause with no exception type, meaning it catches all exceptions. It is bad because it catches KeyboardInterrupt and SystemExit, which are usually meant to terminate the programme. It also makes debugging harder by hiding unexpected errors.

**What does raise do inside an except block without an argument?**

A bare raise inside an except block re-raises the same exception that was caught. It preserves the original traceback, so the exception continues propagating after the handler runs. This is useful for logging errors and then letting the caller handle them.

## Check your understanding

1. **What does the finally block guarantee in a try-except-finally statement?**

   Answer: The finally block guarantees that its code executes even if an exception is raised, caught, or if a return statement is present in the try or except block.

2. **Write a try-except block that catches a ValueError when converting a string to an integer and prints 'Invalid number'.**

   Answer: try: number = int("abc") except ValueError: print("Invalid number")

3. **If you have two except blocks, one for ValueError and one for Exception, which one should come first to ensure both can catch the appropriate exception type?**

   Answer: The more specific except block for ValueError should come first, because Python checks except blocks in order and the first match wins. If Exception comes first, it catches ValueError too.

4. **What happens if you use a bare raise inside an except block?**

   Answer: A bare raise re-raises the same exception that was caught, preserving the original traceback, so the exception continues to propagate to higher-level handlers.

5. **What is the output of this code? def test(): try: return 1 finally: print('done') print(test())**

   Answer: The output is 'done' followed by '1'. The finally block runs before the function returns, printing 'done', then the function returns 1, which is printed by the outer print statement.

---

Interactive version with quiz and diagrams: https://courseiva.com/learn/python-pcap/exception-handling-fundamentals
