# Nested Functions, Closures and nonlocal<no value>

## What will we learn?

- **Nested Function** — a function defined inside a function.
- **Enclosing** — the E of LEGB, up close.
- **Closure** — a function that "remembers" variables.
- **`nonlocal`** — how to change a variable of the enclosing function.

## Why do we need this?

- **A private helper function:** sometimes a function needs a small helper function that belongs **only** to it. There is no reason for it to be global and get mixed up with the rest of the code.
- **Functions that remember state:** in the previous lesson we saw that `make_multiplier(2)` returns a function that "remembers" the number 2. Today we will understand how it works — and how to build a counter, an accumulator or a cache **without** `global`.

## Explanation from the basics

### A function inside a function

You can define a `def` inside a `def`. The inner function:

- Exists **only inside** the outer function — from outside you cannot call it.
- **Sees** the variables and parameters of the outer function.

### Enclosing — the E of LEGB

In the lesson on Scope we learned the lookup order **L → E → G → B**. When an inner function uses a name, Python looks for it:

```text
L  Local       → inside the inner function
E  Enclosing   → inside the outer function
G  Global      → top level of the file
B  Built-in    → print, len, ...
```

### Closure — a function that remembers

When an outer function **returns** the inner function, something interesting happens: the outer function has already ended, but the inner one **still remembers** its variables. This combination — a function + the variables it remembers — is called a **Closure**.

{{< box "analogy" "Analogy: a letter in an envelope" >}}
The outer function writes a letter (the inner function) and puts it in an envelope together with a note with some data (the variables). It sends the envelope and disappears — but whoever opens the envelope gets both the letter and the note. Every envelope has a note of its own.
{{< /box >}}

### nonlocal

**Reading** a variable from the Enclosing scope — is always allowed. **Changing** it (assigning it a new value) — is not possible without a declaration, exactly as with a global. The declaration is `nonlocal name`: "this variable belongs to the enclosing function".

| Declaration | The variable belongs to... |
|---|---|
| `global x` | The file level |
| `nonlocal x` | The nearest enclosing function |

## Syntax

```python
def outer():
    count = 0                 # enclosing variable

    def inner():
        nonlocal count        # assignments go to outer's count
        count += 1
        return count

    return inner              # return the function itself (no parentheses)
```

## First code example

**A private helper function.** `week-2/receipt.py`:

```python
def format_receipt(items):
    def line(name, price):
        return name.ljust(10) + str(price)

    lines = []
    for name, price in items:
        lines.append(line(name, price))
    return "\n".join(lines)

print(format_receipt([("Milk", 6), ("Bread", 9)]))
```

**Output:**

```text
Milk      6
Bread     9
```

## Breaking down the code line by line

| Line | Explanation |
|---|---|
| `def line(name, price):` | An inner function — exists only inside `format_receipt`. |
| `name.ljust(10)` | Pads the name with spaces up to 10 characters, so that the prices stand in a column. |
| `lines.append(line(name, price))` | The outer function uses its helper function. |
| `"\n".join(lines)` | Joins all the lines into one text, with a line break between them. |

## Another example

**A Closure — a function that remembers.** `week-2/greeter.py`:

```python
def make_greeter(greeting):
    def greet(name):
        return greeting + ", " + name
    return greet

hello = make_greeter("Hello")
shalom = make_greeter("Shalom")
print(hello("Dana"))
print(shalom("Avi"))
```

**Output:**

```text
Hello, Dana
Shalom, Avi
```

`make_greeter` already ended on the line `hello = ...`, but `hello` still remembers that `greeting` is `"Hello"`. Every function that was created has **its own copy** of the variable — `shalom` remembers `"Shalom"`.

**A counter with `nonlocal`.** `week-2/counter.py`:

```python
def make_counter():
    count = 0

    def increment():
        nonlocal count
        count += 1
        return count

    return increment

counter = make_counter()
print(counter(), counter(), counter())

other = make_counter()
print(other())
```

**Output:**

```text
1 2 3
1
```

Every counter remembers **its own** `count`: `other` starts from zero, regardless of `counter`. This is private state — without a global variable that all the code can change.

## Common mistake

**The mistake: changing a variable of the enclosing function without `nonlocal`.**

```python
def make_counter():
    count = 0

    def increment():
        count += 1
        return count

    return increment

counter = make_counter()
counter()
```

```text
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
```

**Why?** Because of the assignment `count += 1`, Python decides that `count` is a **local** variable of `increment` — and then tries to read a local that has no value yet. This is exactly the same error we saw with globals in the lesson on Scope.

**The fix:** `nonlocal count` at the start of `increment`.

<!-- 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: An inner helper function

Write a function `format_receipt(items)` that receives a list of pairs (name, price) and returns the text of a receipt. Inside it, define an inner function `line(name, price)` that pads the name to 10 characters (`ljust`).

```python
print(format_receipt([("Milk", 6), ("Bread", 9)]))
```

**Expected output:**

```text
Milk      6
Bread     9
```

{{< solution >}}
```python
def format_receipt(items):
    def line(name, price):
        return name.ljust(10) + str(price)

    lines = []
    for name, price in items:
        lines.append(line(name, price))
    return "\n".join(lines)


print(format_receipt([("Milk", 6), ("Bread", 9)]))
```

```text
Milk      6
Bread     9
```

`line` exists only inside `format_receipt` — it is a private helper function, and does not get mixed up with the rest of the code.
{{< /solution >}}

### Exercise 2: Closure

Write `make_greeter(greeting)` that returns a function which receives a name and returns a greeting.

```python
hello = make_greeter("Hello")
shalom = make_greeter("Shalom")
print(hello("Dana"))
print(shalom("Avi"))
```

**Expected output:**

```text
Hello, Dana
Shalom, Avi
```

{{< solution >}}
```python
def make_greeter(greeting):
    def greet(name):
        return greeting + ", " + name
    return greet


hello = make_greeter("Hello")
shalom = make_greeter("Shalom")
print(hello("Dana"))
print(shalom("Avi"))
```

```text
Hello, Dana
Shalom, Avi
```

`make_greeter` has already ended, but every function it returned **remembers** its own `greeting`. That is a Closure.
{{< /solution >}}

### Exercise 3: Predict the output

```python
def outer():
    message = "outer"

    def show():
        print("show:", message)

    def change():
        nonlocal message
        message = "changed"

    show()
    change()
    show()

outer()
```

{{< solution >}}
```python
def outer():
    message = "outer"

    def show():
        print("show:", message)

    def change():
        nonlocal message
        message = "changed"

    show()
    change()
    show()


outer()
```

```text
show: outer
show: changed
```

`change` uses `nonlocal`, so the assignment changes the `message` of `outer` — it does not create a new local variable.
{{< /solution >}}

### Exercise 4: A counter with a step

Write `make_counter(step)` — every call to the counter increases it by `step` and returns the new value. Create two counters and check that they do not affect each other.

```python
by_two = make_counter(2)
by_ten = make_counter(10)
print(by_two(), by_two())
print(by_ten())
print(by_two())
```

**Expected output:**

```text
2 4
10
6
```

{{< solution >}}
```python
def make_counter(step):
    count = 0

    def increment():
        nonlocal count
        count += step
        return count

    return increment


by_two = make_counter(2)
by_ten = make_counter(10)
print(by_two(), by_two())
print(by_ten())
print(by_two())
```

```text
2 4
10
6
```

- `step` is only **read** inside `increment`, so it needs no declaration.
- `count` **changes**, so it needs `nonlocal count`.
{{< /solution >}}

### Exercise 5: Without nonlocal

```python
def make_counter():
    count = 0
    def increment():
        count += 1
        return count
    return increment

counter = make_counter()
counter()
```

1. Which error will you get, and why?
2. What is the difference between `global` and `nonlocal`?

{{< solution >}}
1. **`UnboundLocalError`**. Because of the assignment `count += 1`, Python decides that `count` is a **local** variable of `increment` — and it has no value when you try to increase it. The fix: `nonlocal count` at the start of `increment`.
2. **`global`** — the variable belongs to the file level. **`nonlocal`** — the variable belongs to the nearest **enclosing** function. A Closure with `nonlocal` gives every counter private state, without a global variable that all the code can change.
{{< /solution >}}

<!-- exercises:end -->

## Summary

- **Nested Function** — a function inside a function. It exists only inside, and sees the variables of the enclosing one (**Enclosing**).
- **Closure** — an inner function that was returned, and remembers the variables of the function that created it. Each one has its own copy.
- **Reading** a variable from the Enclosing scope — is allowed. **Changing** it — only with `nonlocal`, otherwise `UnboundLocalError`.
- `global` — a variable at the file level. `nonlocal` — a variable in the enclosing function.
- A Closure gives **private state** without a global variable. And still — when possible, a parameter in and `return` out is better.

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

## Official documentation

- [Scopes and Namespaces Example](https://docs.python.org/3/tutorial/classes.html#scopes-and-namespaces-example)
- [The nonlocal statement](https://docs.python.org/3/reference/simple_stmts.html#the-nonlocal-statement)

<!-- docs:end -->
