# Exceptions B: else and finally — the Full Structure<no value>

## What will we learn?

In the previous lesson we used `try` and `except`. The structure has two more parts:

- **`else`** — runs **only if there was no error**.
- **`finally`** — runs **always**, with or without an error.

## Why do we need this?

Imagine code that opens a connection to a Database (the backend and databases topics), performs an action and closes the connection. What happens if the action fails in the middle? If the closing is written after the action, it **will not be executed**, and the connection stays open. After a hundred such failures, the server will crash because it ran out of connections.

`finally` guarantees that the cleanup happens **in every case**. `else` keeps the `try` small, and makes it clear what runs only after success.

## Explanation from the basics

### The four blocks

| Block | When it runs | What it is for |
|---|---|---|
| `try` | You always start with it | The code that **may fail** |
| `except` | **Only if** an error of the matching type was thrown | Handling the error |
| `else` | **Only if no** error at all was thrown in the `try` | Continuing that depends on success |
| `finally` | **Always**, at the end, in every case | Cleanup: closing files, connections, a finish message |

{{< box "analogy" "Analogy: cooking" >}}
**try** = you try to bake a cake. **except** = if it burned, you order a cake from the bakery. **else** = if it came out well, you decorate it (you don't decorate a burned cake!). **finally** = in any case, you turn off the oven and wash the dishes.
{{< /box >}}

### Why `else` and not simply continue in the `try`?

You can put the follow-up code inside the `try`. But then, if **that** code fails with the same error, the `except` will catch it by mistake, and handle the wrong problem. `else` makes it clear: **only the risky line inside the `try`**, and the rest outside.

### `finally` runs even when...

- There was an error and it was caught.
- There was an error and it was **not** caught. `finally` runs, and then the program crashes.
- There is a `return` in the middle of the `try`. `finally` runs **before** the function really returns.

## Syntax

```python
try:
    # risky code
except SomeError:
    # runs only if SomeError happened
else:
    # runs only if NO error happened in try
finally:
    # runs ALWAYS, last
```

**Required order:** `try` → `except` (one or more) → `else` (optional) → `finally` (optional). You can also have `try` + `finally` without `except`.

## First code example

`week-2/full_try.py`:

```python
def convert(text):
    print("--- converting:", text)
    try:
        number = int(text)
    except ValueError:
        print("except: not a number")
    else:
        print("else: success, doubled =", number * 2)
    finally:
        print("finally: done with", text)

convert("21")
convert("hi")
```

**Output:**

```text
--- converting: 21
else: success, doubled = 42
finally: done with 21
--- converting: hi
except: not a number
finally: done with hi
```

## Breaking down the code line by line

| Block | `convert("21")` | `convert("hi")` |
|---|---|---|
| `try: number = int(text)` | Succeeds: `number = 21` | `ValueError` |
| `except ValueError:` | Skipped | **Runs** |
| `else:` | **Runs**: prints 42 | Skipped |
| `finally:` | **Runs** | **Runs** |

`else` and `except` **never both run**: either there was an error, or there was not. `finally`, on the other hand, runs **in both cases**.

## Another example

**A "connection" that must be closed.** Here we simulate a connection to an external resource (like a Database), and see that `finally` closes it both when something fails in the middle and when there is a `return`. `week-2/connection_demo.py`:

```python
def open_connection():
    print("[connection opened]")

def close_connection():
    print("[connection closed]")

def fetch_price(product_name, prices):
    open_connection()
    try:
        price = prices[product_name]
    except KeyError:
        print("No such product:", product_name)
        return None
    else:
        print("Found", product_name)
        return price
    finally:
        close_connection()

catalog = {"bread": 8, "milk": 6}

print("Result:", fetch_price("milk", catalog))
print()
print("Result:", fetch_price("cake", catalog))
```

**Output:**

```text
[connection opened]
Found milk
[connection closed]
Result: 6

[connection opened]
No such product: cake
[connection closed]
Result: None
```

**Breakdown:**

- `catalog = {"bread": 8, "milk": 6}` — this is a **dict** (dictionary): every key (`"bread"`) is mapped to a value (`8`). `prices["milk"]` returns 6. We will learn it in depth in the data structures and functions topic. For now it is enough to know that accessing a key that does not exist throws a `KeyError`.
- Notice: there is a `return` both in `except` and in `else`. **And still** `[connection closed]` is printed before the `Result`. That is, `finally` runs **after** the `return` was "decided", but **before** the function really returns.
- Without `finally`, we would have to write `close_connection()` before **every** `return`. Easy to forget, and a lot of duplication.

## Common mistake

**Mistake 1: putting cleanup after the `try`/`except` instead of in `finally`.**

```python
open_connection()
try:
    price = prices[name]
except KeyError:
    print("missing")
    return None
close_connection()   # never reached when except returns!
```

In the case of a `KeyError`, the `return` leaves the function, and `close_connection()` **does not run**. The connection stays open. `finally` solves exactly this.

**Mistake 2: the follow-up code inside the `try`, and an `except` that catches the wrong error.**

```python
try:
    age = int(text)
    category = ["child", "adult"][age // 18]   # can also raise IndexError... or ValueError?
except ValueError:
    print("Age must be a number")
```

If the follow-up line fails, for example with an `IndexError` for age 40 (40 // 18 = 2, and there is no index 2), the message is completely unrelated. Put only `int(text)` inside the `try`, and the follow-up in `else`.

**Mistake 3: thinking that `finally` "catches" the error.** It does not. If there is no matching `except`, `finally` runs, and **after that** the program crashes with a Traceback.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Practice

The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.

### Exercise 1: Predict the output

Write what will be printed for each call — before running it:

```python
def half(text):
    try:
        number = int(text)
    except ValueError:
        print("not a number:", text)
    else:
        print("half is", number / 2)
    finally:
        print("checked", text)

half("8")
half("x")
```

{{< solution >}}
```python
def half(text):
    try:
        number = int(text)
    except ValueError:
        print("not a number:", text)
    else:
        print("half is", number / 2)
    finally:
        print("checked", text)


half("8")
half("x")
```

```text
half is 4.0
checked 8
not a number: x
checked x
```

- For `"8"` there is no error, so `else` runs, followed by `finally`.
- For `"x"` a `ValueError` is thrown, so `except` runs — and `else` does **not** run. `finally` runs in both cases.
{{< /solution >}}

### Exercise 2: finally runs even after return

```python
def check(number):
    try:
        if number > 0:
            return "positive"
        return "not positive"
    finally:
        print("done checking", number)

print(check(5))
print(check(-2))
```

What will be printed, and in which order? Explain why.

{{< solution >}}
```python
def check(number):
    try:
        if number > 0:
            return "positive"
        return "not positive"
    finally:
        print("done checking", number)


print(check(5))
print(check(-2))
```

```text
done checking 5
positive
done checking -2
not positive
```

`finally` runs **before** the function really returns — even when there is a `return` inside the `try`. That is why "done checking" is printed before the value the function returned.
{{< /solution >}}

### Exercise 3: The four blocks

Write a function `parse_quantity(text)` that uses all four blocks:

- `try` — converts the text to a number;
- `except` — prints `invalid: <text>`;
- `else` — prints `quantity: <number>`;
- `finally` — prints `---`.

```python
parse_quantity("3")
parse_quantity("three")
```

**Expected output:**

```text
quantity: 3
---
invalid: three
---
```

{{< solution >}}
```python
def parse_quantity(text):
    try:
        quantity = int(text)
    except ValueError:
        print("invalid:", text)
    else:
        print("quantity:", quantity)
    finally:
        print("---")


parse_quantity("3")
parse_quantity("three")
```

```text
quantity: 3
---
invalid: three
---
```

The code that runs only when everything succeeded is in `else` and not inside the `try` — that way the `try` stays small, and catches only the line that can really fail.
{{< /solution >}}

### Exercise 4: Closing what we opened

The functions `open_register()` and `close_register()` open and close a cash register. Write a function `sell(prices, position)` that opens the register, returns the price at the chosen position (the position is an index of the list — it starts from 0), and **always** closes the register — even when the position does not exist. A position that does not exist returns `None`.

```python
def open_register():
    print("register opened")

def close_register():
    print("register closed")

print(sell([10, 20, 30], 1))
print(sell([10, 20, 30], 7))
```

**Expected output:**

```text
register opened
register closed
20
register opened
register closed
None
```

{{< solution >}}
```python
def open_register():
    print("register opened")


def close_register():
    print("register closed")


def sell(prices, position):
    open_register()
    try:
        return prices[position]
    except IndexError:
        return None
    finally:
        close_register()


print(sell([10, 20, 30], 1))
print(sell([10, 20, 30], 7))
```

```text
register opened
register closed
20
register opened
register closed
None
```

That is exactly the job of `finally`: **cleanup** — closing a resource that was opened, no matter how the function ended.
{{< /solution >}}

### Exercise 5: else or inside the try?

Two developers wrote the same code in two ways:

```text
# A                              # B
try:                             try:
    number = int(text)               number = int(text)
    print(100 / number)          except ValueError:
except ValueError:                   print("not a number")
    print("not a number")        else:
                                     print(100 / number)
```

What is the difference, and which version is better? What will happen in each one when `text` is `"0"`?

{{< solution >}}
- In **A**, the division is also inside the `try`. In **B**, only the conversion is inside the `try`, and the division moved to `else`.
- When `text` is `"0"`, both versions throw a `ZeroDivisionError`, which no `except` catches, and the program crashes. But in **B** it is completely clear that the `except ValueError` belongs **only** to the conversion.
- **B is better**: a `try` as small as possible. That way `except` does not catch by mistake errors that come from other lines, and it is easy to see what each part does.
{{< /solution >}}

<!-- exercises:end -->

## Summary

- **`try`** — risky code. **`except`** — only if a matching error happened. **`else`** — only if there was **no** error. **`finally`** — **always**.
- `except` and `else` never run together.
- `finally` runs even after `return`, and also when the error was not caught (and then the program crashes after it).
- **`else`** keeps the `try` small and focused. **`finally`** is the place for cleanup: closing files and connections.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Official documentation

- [Python Tutorial — Defining Clean-up Actions](https://docs.python.org/3/tutorial/errors.html#defining-clean-up-actions)
- [The try statement](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement)

<!-- docs:end -->
