πŸ”’ Python Closures β€” Functions That Remember Their Environment

Introduction 🌟

A closure in Python is a function that remembers the values from its enclosing scope even after that scope has finished executing. Closures enable powerful programming patterns like data hiding, function factories, and decorators.

Note

πŸ’‘ A closure = inner function + free variables + remembered environment.

1. Basic Closure Structure 🧱

basic_closure.py

def outer():
    msg = "Hello"

    def inner():
        print(msg)  # uses variable from outer function
    return inner

fn = outer()
fn()  # prints "Hello"

βœ” Even though outer() has finished, inner() still remembers msg.

2. How Closures Work Internally πŸ”

  • Inner function uses variables from outer function.
  • Outer function returns the inner function.
  • Inner function keeps a reference to the outer function's variables.

3. Checking Closure Variables πŸ“¦

closure_vars.py

def make_printer(msg):
    def printer():
        print(msg)
    return printer

p = make_printer("Hello Python")
print(p.__closure__)   # contains captured variables

4. Closure for Function Factories 🏭

function_factory.py

def power(n):
    def calc(x):
        return x ** n
    return calc

square = power(2)
cube = power(3)

print(square(5))  # 25
print(cube(3))    # 27

Note

βœ” Closures allow dynamic creation of functions with preset behavior.

5. Closures for Data Hiding πŸ”

closure_data_hiding.py

def secret_counter():
    count = 0

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

    return increment

counter = secret_counter()
print(counter())  # 1
print(counter())  # 2

βœ” count is private β€” cannot be accessed outside.

6. Using nonlocal with Closures 🧠

nonlocal lets inner functions modify variables from enclosing scopes.

nonlocal_example.py

def bank_account():
    balance = 1000

    def deposit(amount):
        nonlocal balance
        balance += amount
        return balance

    return deposit

acc = bank_account()
print(acc(200))  # 1200

7. Closures in Decorators πŸŽ€

Decorators rely heavily on closures.

decorator_closure.py

def log(func):
    def wrapper():
        print("Calling", func.__name__)
        return func()
    return wrapper

@log
def greet():
    print("Hello")

greet()

Note

βœ” wrapper() remembers func through closure.

8. Real-World Example: Authentication πŸ”

auth_closure.py

def auth(role):
    def decorator(func):
        def wrapper():
            if role != "admin":
                return "Access denied"
            return func()
        return wrapper
    return decorator

@auth("admin")
def dashboard():
    return "Welcome Admin"

print(dashboard())

9. Real-World Example: Event Counter πŸ”’

event_counter.py

def event_counter():
    count = 0

    def track():
        nonlocal count
        count += 1
        return f"Event triggered {count} times"

    return track

event = event_counter()
print(event())
print(event())
print(event())

10. When to Use Closures? 🎯

  • When you want to hide data inside functions (encapsulation).
  • When creating function factories.
  • When writing decorators.
  • When you need persistent state without classes.

11. Common Mistakes ⚠️

  • Trying to modify outer variables without nonlocal.
  • Capturing variables incorrectly in loops.
  • Forgetting that closures store references, not snapshots.

closure_loop_pitfall.py

funcs = []
for i in range(3):
    funcs.append(lambda: i)  # all return 2

print([f() for f in funcs])

Note

βœ” Use default arguments or closures carefully to avoid this pitfall.

Conclusion πŸŽ‰

>>β€œClosures allow functions to remember β€” enabling elegant, powerful, and flexible program design.” ✨

You now fully understand Closures in Python! Want the next topic? Try Modules, OOP (Classes & Objects), Scopes, or Decorators with Parameters. Just tell me! 😊