Python Decorators – The Complete Guide with Deep Explanation

M Naeem BanGashM Naeem BanGash
2 min read

1. What is a Decorator?

In Python, functions are first-class citizens, meaning:

  • They can be stored in variables.

  • Passed as arguments.

  • Returned from other functions.

A decorator is simply a function that takes another function and adds extra behavior to it without changing its source code.


Basic Example

def my_decorator(func):
    def wrapper():
        print("Before the function runs")
        func()
        print("After the function runs")
    return wrapper

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

say_hello()

Output:

Before the function runs
Hello, World!
After the function runs

2. How Decorators Work

When you write:

@my_decorator
def say_hello():
    pass

It’s actually:

say_hello = my_decorator(say_hello)

That means the original say_hello is replaced by the wrapper function.


3. Decorators with Arguments

If your function has parameters, the decorator’s wrapper should accept them.

def log_arguments(func):
    def wrapper(*args, **kwargs):
        print(f"Arguments: {args} {kwargs}")
        return func(*args, **kwargs)
    return wrapper

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

print(add(3, 5))

Output:

Arguments: (3, 5) {}
8

4. Preserving Function Metadata

Without care, decorators overwrite the original function’s name & docstring.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def example():
    """This is an example function."""
    pass

print(example.__name__)  # wrapper, not example

Solution: Use functools.wraps

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

5. Real-World Uses of Decorators

  • Logging

  • Authentication checks

  • Caching results

  • Timing function execution

  • Access control


Example: Timing a Function

import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f} seconds")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(2)
    print("Done!")

slow_function()

Output:

Done!
slow_function took 2.0003 seconds

6. Class-Based Decorators

You can also create decorators using classes.

class Repeat:
    def __init__(self, times):
        self.times = times

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            for _ in range(self.times):
                func(*args, **kwargs)
        return wrapper

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

greet("Naeem")

Output:

CopyEditHello, Naeem!
Hello, Naeem!
Hello, Naeem!

7. Summary

  • Decorators wrap functions to add functionality.

  • Use @decorator_name syntax.

  • functools.wraps keeps metadata.

  • Useful for logging, authentication, performance tracking, etc.

0
Subscribe to my newsletter

Read articles from M Naeem BanGash directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

M Naeem BanGash
M Naeem BanGash