Fundamentals 8 min read

Unlock Python Power: Master Decorators in Minutes

This article explains Python decorators—from basic analogies and the underlying concepts of higher‑order functions and closures to practical examples like timing and logging decorators—showing how to enhance functions dynamically without altering their source code while preserving metadata with functools.wraps.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Unlock Python Power: Master Decorators in Minutes

Python developers often need to add features such as logging, timing, or permission checks to a function without touching its internal code. Decorators provide an elegant, powerful way to achieve this.

1. What is a decorator? A simple analogy

Imagine a core function (e.g., "deliver a package") that is pure. Before the delivery you might make a phone call, and after delivery you might add a signature. A decorator works like a "package wrapper" that adds these steps before and after the core function.

It takes your core function.

Adds new behavior (e.g., a phone call and a signature).

Returns a brand‑new, enhanced function.

2. Core foundation: Understanding closures and higher‑order functions

1. Higher‑order function

A function can be passed as an argument to another function and can also be returned as a value. This is the skeleton of a decorator.

def outer_func(func):
    def inner_func():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return inner_func

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

enhanced_say_hello = outer_func(say_hello)
enhanced_say_hello()

2. Closure

The inner function remembers and accesses the outer function’s local variables (e.g., func) even after the outer function has finished executing. This is the soul of a decorator.

3. Magic moment: @ syntax sugar

Python’s @ symbol is a convenient syntactic sugar that makes applying decorators extremely simple.

def outer_func(func):
    def inner_func():
        print("Before calling...")
        func()
        print("After calling...")
    return inner_func

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

say_hello()  # actually calls the decorated version

The line @outer_func is equivalent to say_hello = outer_func(say_hello).

4. Practical: Writing useful decorators

Case 1: Timer decorator

import time

def timer(func):
    """Calculate the execution time of a function"""
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        run_time = end_time - start_time
        print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
        return result
    return wrapper

@timer
def long_running_function(n):
    """Simulate a time‑consuming function"""
    for i in range(n):
        sum([j**2 for j in range(10000)])
    return "Done"

result = long_running_function(10)
print(result)

Case 2: Logging decorator

def logger(func):
    """Log function call information"""
    def wrapper(*args, **kwargs):
        print(f"[INFO] Calling function: {func.__name__}")
        print(f"[INFO] Arguments: {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"[INFO] Function {func.__name__} finished.")
        return result
    return wrapper

@logger
def add_numbers(a, b):
    return a + b

print(add_numbers(3, b=4))

5. Solving a side effect of decorators

After decoration, the original function’s metadata (e.g., __name__, __doc__) is replaced by the wrapper’s metadata. Use functools.wraps to preserve the original information.

import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        print(f"Finished {func.__name__!r} in {end_time - start_time:.4f} secs")
        return result
    return wrapper

@timer
def long_running_function(n):
    ...

print(long_running_function.__name__)  # correctly prints 'long_running_function'

6. Summary and key points

Essence: a higher‑order function that returns a function, leveraging closures.

Purpose: dynamically enhance a function without modifying its code, following the open‑closed principle.

Core: the @ syntax is merely syntactic sugar for func = decorator(func).

Best practice: always use functools.wraps to retain the original function’s metadata.

Advantages: decoupling, code reuse, and flexible composition (multiple decorators can be stacked).

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Pythonprogrammingcode-exampleDecoratorclosureHigher-order function
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

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.