Fundamentals 14 min read

Understanding Python Closures and Decorators

This article explains Python closures and decorators, detailing their definitions, formation conditions, practical examples, common use cases, and how decorators rely on closures, while providing code snippets for counters, timing, and repeatable execution.

php中文网 Courses
php中文网 Courses
php中文网 Courses
Understanding Python Closures and Decorators

After mastering the basic usage of Python functions, we dive into two powerful and expressive features: closures and decorators. They make code more concise and elegant, and are key to understanding many Python frameworks (such as Flask, Django) and advanced programming techniques.

1. Closure (Closure)

1. What is a closure?

A closure is a function that remembers and can access its lexical scope even when the function is called outside that scope.

Specifically, a closure is formed when the following three conditions are met:

There is a nested function: a function defined inside another function.

The inner function references a variable from the outer function's (non‑global) scope.

The outer function returns the inner function.

When the outer function finishes execution and returns the inner function, some variables from the outer scope are captured by the inner function instead of being discarded. The inner function that carries those captured variables is the closure.

2. Formation of a closure – example

Consider a classic counter example:

def create_counter(initial_count=0):
    """Create a counter closure"""
    count = initial_count  # free variable

    def counter():
        """Inner function that accesses and modifies the outer variable 'count'"""
        nonlocal count  # declare that 'count' is from the outer (enclosing) scope
        count += 1
        print(f"Current count: {count}")
        return count

    return counter  # return the inner function

# Create two independent counter instances
counter1 = create_counter()
counter2 = create_counter(10)

# Call the counters
counter1()  # Output: Current count: 1
counter1()  # Output: Current count: 2
counter2()  # Output: Current count: 11
counter1()  # Output: Current count: 3

print(f"Type of counter1: {type(counter1)}")  #
# print(counter1.__closure__)  # inspect the closure (optional)

Analysis

create_counter is the outer function, counter is the inner function.

The inner function counter references the variable count defined in create_counter .

create_counter returns the inner function counter .

When counter1 = create_counter() is executed, count is initialized to 0 and the inner counter function is returned, remembering that initial value.

Each call to counter1() accesses and updates the captured count variable.

counter2 is created with an initial count of 10, so its state is independent of counter1 .

The nonlocal keyword is required to modify the captured variable; without it, Python would treat count as a new local variable.

3. Common uses of closures

Data encapsulation and privacy: hide internal state (e.g., count ) while exposing only necessary operations.

State retention: maintain state across multiple calls (counters, caches, etc.).

Lazy evaluation: defer computation until needed.

Implementing decorators: closures are the core mechanism behind decorators.

2. Decorator (Decorator)

1. What is a decorator?

A decorator is a function that takes another function as an argument and returns a new function (or a modified version of the original). It allows adding extra functionality (logging, performance testing, permission checks, transaction handling, etc.) without changing the original function's code.

Python provides the @ syntactic sugar to apply decorators, making the code more concise and readable.

2. Basic structure and workflow

A simple decorator looks like this:

import functools  # preserve original function metadata

def my_decorator(func):
    """A simple decorator"""
    @functools.wraps(func)  # keep __name__, __doc__, etc.
    def wrapper(*args, **kwargs):
        print(f"Preparing to execute function: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"Function {func.__name__} finished execution.")
        return result
    return wrapper

Workflow

When you write @my_decorator above a function definition, Python internally does:

# def say_hello(name):
#     """A simple greeting function"""
#     print(f"Hello, {name}!")

# say_hello = my_decorator(say_hello)  # key step!

Thus, the name say_hello now refers to the wrapper function returned by my_decorator , which internally calls the original say_hello .

functools.wraps purpose

Without @functools.wraps(func) , the decorated function would lose its original metadata ( __name__ , __doc__ ) and appear as wrapper , making debugging and documentation harder. wraps copies the original metadata onto the wrapper.

3. Decorator example: timer

import time
import functools

def timer_decorator(func):
    """Decorator that measures execution time of a function"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"Function {func.__name__!r} took {end_time - start_time:.4f} seconds")
        return result
    return wrapper

@timer_decorator
def complex_calculation(n):
    """Simulate a time‑consuming calculation"""
    total = 0
    for i in range(n):
        total += i * i
    print(f"Result: {total}")
    return total

# Call the decorated function
complex_calculation(1000000)

4. Parameterized decorators

Sometimes a decorator itself needs parameters for flexible configuration. This requires an extra level of nesting, creating a “decorator factory”.

import functools

def repeat(num_times):
    """Decorator factory that returns a decorator which repeats the target function"""
    def decorator_repeat(func):
        """Actual decorator"""
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_result = None
            for _ in range(num_times):
                print(f"Executing {func.__name__} for the { _ + 1 }‑th time")
                last_result = func(*args, **kwargs)
            return last_result  # return result of the last call
        return wrapper
    return decorator_repeat

# Use the parameterized decorator
@repeat(num_times=3)
def greet(name):
    """Simple greeting"""
    print(f"Hello, {name}!")

greet("Python learner")

Workflow of @repeat(num_times=3)

First, repeat(num_times=3) is called, returning decorator_repeat .

Python then passes the function greet to decorator_repeat , which returns wrapper .

The name greet is finally bound to wrapper , so calling greet(...) actually invokes wrapper(...) , which runs the original greet three times.

In effect, @repeat(num_times=3) is equivalent to greet = repeat(num_times=3)(greet) .

3. Relationship between closures and decorators

From the examples above, decorators are a typical application of closures.

Decorator functions (e.g., my_decorator , timer_decorator , decorator_repeat ) define an inner wrapper function.

The wrapper captures variables from the outer decorator’s scope, most importantly the original function func .

The decorator returns this wrapper function.

Thus, wrapper is itself a closure that “remembers” the original function and any parameters supplied to a parameterized decorator (such as num_times ). When invoked, it can execute extra logic before/after calling func .

Summary

Closures are a functional programming concept that allow an inner function to retain access to variables from its enclosing (non‑global) scope even after the outer function has finished.

Decorators provide a clean syntax for augmenting functions without modifying their source code, widely used for logging, performance testing, permission control, etc.

Decorators are typically implemented using closures; understanding closures is essential to grasp how decorators work.

Using functools.wraps is best practice when writing robust decorators, as it preserves the original function’s metadata.

Mastering closures and decorators will greatly improve the efficiency, modularity, and expressiveness of your Python code.

Java learning materials

C language learning materials

Frontend learning materials

C++ learning materials

PHP learning materials

Pythoncode examplesdecoratorClosureHigher-order FunctionsFunction Wrappingnonlocal
php中文网 Courses
Written by

php中文网 Courses

php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.