Python: Decorators and Closures

Decorators are one of Python's most elegant features. They let you "add extras" to functions without modifying the original code — adding logging, timing, permission checks... Many frameworks and libraries use decorators extensively. Understanding them is an important milestone in Python mastery.


1. Closures

Closures are the foundation of decorators. Simply put: an inner function remembers variables from the outer function, even after the outer function has finished executing.

PYTHON
def make_multiplier(n):
    """Create a multiplication function"""
    def multiplier(x):
        return x * n        # Inner function remembers the outer variable n
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)

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

Even though double and triple come from the same factory function make_multiplier, they each remember different n values (2 and 3). This is a closure — a function + the external variables it remembers.

Tip: Closure use cases: 1) Factory functions (creating differently configured functions as above). 2) Decorators (standard implementation). 3) Saving context in callback functions.



2. Decorator Basics

A decorator is a function that takes a function as an argument and returns a new function:

PYTHON
def my_decorator(func):
    def wrapper():
        print("=== Before function execution ===")
        func()
        print("=== After function execution ===")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

Output:

TEXT 📖 Display only
=== Before function execution ===
Hello!
=== After function execution ===

@my_decorator is equivalent to say_hello = my_decorator(say_hello). The syntax sugar makes the code cleaner.

▶ Example: Logging Decorator (Difficulty: Star-Star)

PYTHON
def log_call(func):
    """Log function calls"""
    def wrapper(*args, **kwargs):
        print(f"[LOG] Calling {func.__name__}({args}, {kwargs})")
        result = func(*args, **kwargs)
        print(f"[LOG] {func.__name__} returned {result}")
        return result
    return wrapper

@log_call
def add(a, b):
    return a + b

@log_call
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

print(add(3, 5))
print(greet("Zhang San"))
▶ Try it Yourself

Output:

TEXT 📖 Display only
[LOG] Calling add((3, 5), {})
[LOG] add returned 8
8
[LOG] Calling greet(('Zhang San',), {'greeting': 'Hello'})
[LOG] greet returned Hello, Zhang San!


3. functools.wraps: Preserving Original Function Info

Decorators "replace" the original function, causing the function name and docstring to be lost. @wraps fixes this:

PYTHON
from functools import wraps

def my_decorator(func):
    @wraps(func)                    # Preserve original function info
    def wrapper(*args, **kwargs):
        """Decorator inner function"""
        print("Before function execution...")
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def say_hello():
    """A simple greeting function"""
    print("Hello!")

print(say_hello.__name__)           # say_hello (without @wraps, would show wrapper)
print(say_hello.__doc__)            # A simple greeting function (without @wraps, would show decorator inner function)

Tip: Always add @wraps when writing decorators. Without it, the decorated function's name and documentation are lost, making debugging difficult. Import wraps from functools and apply @wraps(func) to the inner wrapper function.



4. Parameterized Decorators

Sometimes the decorator itself needs parameters:

PYTHON
from functools import wraps

def repeat(times=2):
    """Repeat execution a specified number of times"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hello, {name}!")

greet("Zhang San")

Output:

TEXT 📖 Display only
Hello, Zhang San!
Hello, Zhang San!
Hello, Zhang San!

▶ Example: Timing Decorator (Difficulty: Star-Star-Star)

PYTHON
import time
from functools import wraps

def timer(func):
    """Calculate function execution time"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"[Timer] {func.__name__} took {elapsed:.4f} seconds")
        return result
    return wrapper

@timer
def slow_function():
    total = 0
    for i in range(1000000):
        total += i
    return total

result = slow_function()
print(f"Result: {result}")
▶ Try it Yourself

Output (timing varies by computer):

TEXT 📖 Display only
[Timer] slow_function took 0.0452 seconds
Result: 499999500000


5. Combining Multiple Decorators

▶ Example: Simple Timing Decorator (Difficulty ⭐)

PYTHON
import time
from functools import wraps

def simple_timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"[{func.__name__}] took {elapsed:.6f} seconds")
        return result
    return wrapper

@simple_timer
def sum_range(n):
    total = 0
    for i in range(1, n + 1):
        total += i
    return total

@simple_timer
def greet(name):
    return f"Hello, {name}!"

result = sum_range(100000)
print(f"Sum result: {result}")
print(greet("Alice"))
▶ Try it Yourself

Output:

TEXT 📖 Display only
[sum_range] took 0.008123 seconds
Sum result: 5000050000
[greet] took 0.000001 seconds
Hello, Alice!


5. Combining Multiple Decorators

A function can have multiple decorators stacked. Execution order is from bottom to top:

PYTHON
from functools import wraps

def bold(func):
    @wraps(func)
    def wrapper():
        return f"<b>{func()}</b>"
    return wrapper

def italic(func):
    @wraps(func)
    def wrapper():
        return f"<i>{func()}</i>"
    return wrapper

@bold
@italic
def hello():
    return "Hello"

print(hello())              # <b><i>Hello</i></b>
# Execution order: first @italic, then @bold

# Equivalent to: hello = bold(italic(hello))

Tip: Multiple decorators execute from bottom to top. Think of the function being "wrapped" layer by layer — the bottom decorator is closest to the original function and executes first. Understanding this order keeps multiple decorators from causing confusion.



6. Common Use Cases


❓ FAQ

Q What's the relationship between decorators and closures?
A Decorators are implemented using closures. A closure is "a function that remembers external variables." Decorators leverage closures so the inner wrapper function can access the func parameter passed from the outer function.
Q What useful decorators come with Python?
A @property (attribute access), @staticmethod (static methods), @classmethod (class methods), @functools.lru_cache() (caching), @functools.wraps (preserving function info), @dataclass (data classes). All are in the standard library.

📖 Summary


📝 Exercises

  1. Beginner (Difficulty: Star): Write a print_call decorator that prints "Calling xxx function" when the function executes.

  2. Intermediate (Difficulty: Star-Star): Write a retry(max_attempts=3) decorator that automatically retries the specified number of times when a function raises an exception. If retries are exhausted, raise the final exception. Hint: Use a for loop + try-except in the wrapper.

  3. Advanced (Difficulty: Star-Star-Star): Write a parameterized decorator cache_result(ttl=60) that caches the function's return value. Within the TTL seconds, calling with the same parameters returns the cached result; beyond the TTL, recompute the value.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏