# Closure In Python

By the end of this lesson, you’ll:

* Know **what a closure is**,
    
* Understand **how closures work internally**,
    
* Learn **why we use closures**,
    
* See **practical real-world examples**,
    
* And get **practice exercises** to solidify your understanding.
    

---

## 1\. What is a Closure in Python?

A **closure** is a **function that remembers variables from its enclosing scope**, even if that scope is no longer active.

In other words:

> A closure allows a function to access variables from outside its immediate scope, even after the outer function has finished executing.

---

### Example (without closure)

```python
def outer_function():
    x = 10
    def inner_function():
        print(x)
    inner_function()

outer_function()

```

**Output:**

```plaintext
10

```

Here, `inner_function()` accesses `x` from the outer scope — that’s normal.

But now watch what happens if we return the inner function.

---

## 2\. How Closures Are Created

### Step-by-step Example:

```python
def outer_function():
    x = 10   # <- This variable is in the enclosing scope

    def inner_function():
        print(x)  # <- inner_function refers to x

    return inner_function   # <- returning the function, not calling it

my_func = outer_function()  # outer_function() executes and returns inner_function
my_func()  # we call the returned function

```

**Output:**

```plaintext
10

```

---

### What happened internally?

* When `outer_function()` ran, it created `x = 10` and defined `inner_function`.
    
* Then it **returned** `inner_function`.
    
* Normally, when a function finishes, its local variables (`x`) are destroyed.
    
* But because `inner_function` **remembers** `x`, it stays alive inside the returned function.
    

That “remembering” is called a **closure**.

---

## 3\. Checking if a function is a closure

You can inspect the closure using the `__closure__` attribute:

```python
def outer_function():
    x = 5
    def inner_function():
        print(x)
    return inner_function

f = outer_function()
print(f.__closure__)  # <--- contains the remembered variables

```

**Output:**

```plaintext
(<cell at 0x...: int object at 0x...>,)

```

You can even look inside the closure cell:

```python
print(f.__closure__[0].cell_contents)

```

**Output:**

```plaintext
5

```

That’s how Python stores the enclosed variable.

---

## 4\. Why Use Closures?

Closures are very powerful and used frequently in Python to:

1. **Preserve state** without using global variables or classes.
    
2. **Encapsulate logic** neatly.
    
3. **Build function factories** (functions that return customized functions).
    
4. **Implement decorators** (a key part of advanced Python).
    

---

### Example 1: Function Factory

You can use closures to create customized functions.

```python
def multiplier(factor):
    def multiply(number):
        return number * factor
    return multiply

double = multiplier(2)
triple = multiplier(3)

print(double(5))  # 10
print(triple(5))  # 15

```

Here:

* Each returned function (`double`, `triple`) “remembers” its own `factor`.
    
* `factor` stays alive even after `multiplier()` finishes.
    

---

### Example 2: Simple Counter

Closures can maintain state between function calls.

```python
def counter():
    count = 0
    def increment():
        nonlocal count   # allows modification of outer variable
        count += 1
        return count
    return increment

count1 = counter()
print(count1())  # 1
print(count1())  # 2
print(count1())  # 3

```

Without `nonlocal`, Python would treat `count` as a new local variable inside `increment()`.

---

### Example 3: Configuration Wrapper

Suppose you want to wrap behavior around a specific configuration.

```python
def greet_config(lang):
    def greet(name):
        if lang == "en":
            print(f"Hello, {name}!")
        elif lang == "es":
            print(f"Hola, {name}!")
        else:
            print(f"Hi, {name}!")
    return greet

english_greet = greet_config("en")
spanish_greet = greet_config("es")

english_greet("Alice")
spanish_greet("Carlos")

```

Output:

```plaintext
Hello, Alice!
Hola, Carlos!

```

Each function remembers its `lang` configuration.

---

## 5\. Important Keyword: `nonlocal`

When using closures, the **inner function cannot modify** variables from the enclosing scope **unless** you declare them as `nonlocal`.

Example:

```python
def outer():
    count = 0
    def inner():
        nonlocal count
        count += 1
        return count
    return inner

f = outer()
print(f())  # 1
print(f())  # 2

```

Without `nonlocal`, you’d get an error:

```plaintext
UnboundLocalError: local variable 'count' referenced before assignment

```

---

---

## 7\. Real-World Use Cases of Closures

1. **Decorators** — Python decorators are implemented using closures.
    
    ```python
    def logger(func):
        def wrapper(*args, **kwargs):
            print(f"Running {func.__name__}...")
            return func(*args, **kwargs)
        return wrapper
    
    @logger
    def greet(name):
        print(f"Hello, {name}")
    
    greet("John")
    
    ```
    
2. **Data hiding** — Closures let you encapsulate variables.
    
3. **Callbacks** — Used in event handling (e.g., in GUIs or async code).
    
4. **Custom Function Generators** — Like `multiplier()` above.
    

---

## 8\. Practice Exercises

**Exercise 1: Function Factory**

Create a closure `power_of(n)` that returns a function which raises any number to the power `n`.

Expected:

```python
square = power_of(2)
cube = power_of(3)

print(square(4))  # 16
print(cube(2))    # 8

```

---

**Exercise 2: Simple Banking Example**

Create a closure `bank_account()` that:

* Starts with a balance of 0.
    
* Has an inner function `transaction(amount)` that can deposit or withdraw.
    
* Uses `nonlocal` to modify the balance.
    

Expected:

```python
account = bank_account()
print(account(100))   # Deposited 100 → balance = 100
print(account(-50))   # Withdrawn 50 → balance = 50

```

---

## 9\. Summary

| Concept | Meaning |
| --- | --- |
| **Closure** | A function that remembers variables from its enclosing scope |
| **How it’s formed** | When a nested function references variables from its enclosing function and is returned |
| **Use cases** | Function factories, decorators, maintaining state, data hiding |
| **Keyword** | `nonlocal` allows modification of enclosed variables |

---

## Key Takeaways

* Closures make functions *stateful* without using classes.
    
* They are foundational for decorators and many advanced Python features.
    
* They provide **encapsulation** and **flexibility** in functional programming styles.
    

---
