Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding Python Closures and Function Decorators

Tech 1

Building reusable code often leads to situations where existing functions need extra behavior without altering their original implementation. Python supports this through closures and decorators.

How Closures Work

A closure occurs when a nested function references a variable from its enclosing scope and the enclosing function returns the nested functon itself.

Consider this structure:

def outer(prefix):
    def inner(message):
        print(f"{prefix}: {message}")
    return inner

log = outer("INFO")
log("Service started")

The inner function captures prefix from outer. Calling outer("INFO") returns inner, which is assigned to log. Invoking log later still has access to the captured prefix, producing "INFO: Service started". This delayed execution is the core of closures.

Writing Decorators

A decorator is a callable that wraps another function to extend its behavior. Its essentially a closure where the outer function receives a callable instead of a simple value.

Define a decorator that measures execution time:

import time

def timer(original_func):
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = original_func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{original_func.__name__} took {end - start:.4f}s")
        return result
    return wrapper

Decorate a function with @timer:

@timer
def compute_squares(limit):
    return [i * i for i in range(limit)]

values = compute_squares(10000)
# Output: compute_squares took 0.0012s

The @timer syntax is equivalent to compute_squares = timer(compute_squares). The wrapper runs extra logic before and after the original call, keeping the decorated function's API unchanged.

Stacking Multiple Decorators

You can apply several decorators to the same function. Execution order follows how the decorators are stacked:

def bold(fn):
    def wrapper(*args, **kwargs):
        return f"<b>{fn(*args, **kwargs)}</b>"
    return wrapper

def italic(fn):
    def wrapper(*args, **kwargs):
        return f"<i>{fn(*args, **kwargs)}</i>"
    return wrapper

@bold
@italic
def greet(name):
    return f"Hello, {name}"

print(greet("Alice"))  # <b><i>Hello, Alice</i></b>

Here italic is applied first, then bold wraps the result, producing combined markup.

Practical Use Cases

  • Access control: validate user permissions before executing a view function.
  • Logging: automatically record function calls, arguments, and return values.
  • Caching: store results of expensive computations to avoid repeated work.

Decorators leverage closures to inject cross-cutting concerns cleanly and readably.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.