Fundamentals 10 min read

Master Python Decorators: Boost Your Functions in Minutes

This article explains Python decorators with clear analogies, walks through the underlying concepts of higher‑order functions and closures, provides practical code examples such as timing and logging decorators, discusses common pitfalls like lost metadata, and shows how to preserve it using functools.wraps.

Python Crawling & Data Mining
Python Crawling & Data Mining
Python Crawling & Data Mining
Master Python Decorators: Boost Your Functions in Minutes

1. What is a decorator? A simple metaphor

Imagine a core function like a delivery service. A decorator works like a packaging worker who adds steps before and after the delivery—such as a phone call before sending and a signature after—without changing the core logic.

Decorator metaphor
Decorator metaphor

It takes your core function.

Adds new behavior (e.g., logging, timing) around it.

Returns a new, enhanced function.

The essence is adding functionality dynamically without modifying the original function’s code.

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

2.1 Higher‑order function

A function that can accept another function as an argument and/or return a function. 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()

Output:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

2.2 Closure

The inner function remembers the variables of the outer function even after the outer function has finished, which is the soul of a decorator.

Closure illustration
Closure illustration

3. Magic moment: The @ syntax sugar

Python’s @ symbol provides a concise way to apply a decorator.

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()

Using @outer_func is equivalent to say_hello = outer_func(say_hello). The execution order is:

Define the original function.

Pass it to the decorator.

Replace the original name with the returned wrapper.

@ syntax diagram
@ syntax diagram

4. Hands‑on: Useful decorator examples

4.1 Timer decorator

import time

def timer(func):
    """Calculate function execution time"""
    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)

Sample output:

Finished 'long_running_function' in 0.1234 secs
Done

4.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))

Sample output:

[INFO] Calling function: add_numbers
[INFO] Arguments: (3,), {'b': 4}
[INFO] Function add_numbers finished.
7

5. Solving a common side‑effect

After decoration, the original function’s metadata (e.g., __name__, __doc__) is replaced by the wrapper’s metadata.

print(long_running_function.__name__)  # Outputs: 'wrapper'

Solution: use functools.wraps inside the decorator to preserve the original metadata.

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

6. Summary and key takeaways

Key Point

Description

Essence

A higher‑order function that returns a function, leveraging closures.

Purpose

Dynamically enhance function behavior without altering its internal code, following the open‑closed principle.

Core

The @ syntax is just syntactic sugar for func = decorator(func).

Best practice

Always apply functools.wraps to keep the original function’s metadata.

Advantages

Decoupling, code reuse, and flexible composition (multiple decorators can be stacked).

Decorators appear everywhere in Python—from web frameworks ( @app.route) to testing ( @patch) and plugin systems. Mastering them elevates your Python expertise to a new level.

PythonclosureHigher-order functionBest practice
Python Crawling & Data Mining
Written by

Python Crawling & Data Mining

Life's short, I code in Python. This channel shares Python web crawling, data mining, analysis, processing, visualization, automated testing, DevOps, big data, AI, cloud computing, machine learning tools, resources, news, technical articles, tutorial videos and learning materials. Join us!

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.