Decorators in Python Explained | Guide for Beginners & Advanced Users

Decorators in Python: If you’re coding in Python, chances are you’ve already used decorators—perhaps without even realizing it. Whether you’re a beginner just discovering functions or an advanced Pythonista brushing up on best practices, decorators are a concept you must understand deeply.

This article walks you through Decorators in Python, starting from the basics and gradually moving to advanced use cases, so you come out the other side ready to wield them like a pro.

🎯 What is a Decorator in Python?

A decorator in Python is a function that modifies the behavior of another function (or method or class) without changing its source code.

Think of decorators as wrappers that add extra functionality to existing code in a clean, readable way.

Basic Structure:

def decorator_function(original_function):

    def wrapper_function():

        print("Wrapper executed before the original function.")

        return original_function()

    return wrapper_function

Apply it to a function:

@decorator_function

def say_hello():

    print("Hello!")

say_hello()

Output:

Wrapper executed before the original function.

Hello!


🔍 Why Use Decorators?

  • Code Reusability – You don’t need to copy/paste logic across multiple functions.
  • Separation of Concerns – Keeps logic modular (e.g., logging, timing, access control).
  • Cleaner Syntax – The @decorator syntax is elegant and improves readability.
  • Meta-programming – Allows you to modify behavior at runtime.

🛠️ Creating Your First Custom Decorator

Let’s say you want to log every time a function is called.

def logger(func):

    def wrapper(*args, **kwargs):

        print(f"Function '{func.__name__}' was called with args: {args}, kwargs: {kwargs}")

        return func(*args, **kwargs)

    return wrapper

@logger

def greet(name):

    print(f"Hello, {name}!")

greet("Alice")

Output:

Function ‘greet’ was called with args: (‘Alice’,), kwargs: {}

Hello, Alice!


🎛️ Decorators with Arguments

Need to pass parameters to your decorator? That requires three levels of nested functions.

def repeat(num_times):

    def decorator(func):

        def wrapper(*args, **kwargs):

            for _ in range(num_times):

                func(*args, **kwargs)

        return wrapper

    return decorator

@repeat(3)

def say_hi():

    print("Hi!")

say_hi()

Output:

Hi!

Hi!

Hi!


🧠 Advanced Usage: Built-in Decorators and functools

1. @staticmethod and @classmethod

class MyClass:

    @staticmethod

    def static_method():

        print("I don't need class or instance.")

    @classmethod

    def class_method(cls):

        print(f"I got the class: {cls}")

2. Using functools.wraps

One common mistake is forgetting to preserve metadata (like __name__, __doc__) when wrapping a function.

import functools

def debug(func):

    @functools.wraps(func)

    def wrapper(*args, **kwargs):

        print(f"Calling {func.__name__}")

        return func(*args, **kwargs)

    return wrapper

Without @functools.wraps, the decorated function would lose its original name and docstring.


🧪 Real-World Use Cases of Decorators

✅ Logging

def log_execution(func):

    def wrapper(*args, **kwargs):

        print(f"{func.__name__} is executing...")

        result = func(*args, **kwargs)

        print(f"{func.__name__} execution finished.")

        return result

    return wrapper

✅ Timing

import time

def timer(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

✅ Access Control / Authentication

def requires_admin(func):

    def wrapper(user, *args, **kwargs):

        if not user.get('is_admin'):

            raise PermissionError("Admin access required.")

        return func(user, *args, **kwargs)

    return wrapper


🧵 Stacking Multiple Decorators

You can apply more than one decorator to a single function:

@timer

@logger

def process_data(data):

    time.sleep(1)

    return f"Processed {data}"

Decorator order matters! The bottom one is applied first.


⚠️ Common Pitfalls

  • Forgetting to return the wrapped function.
  • Ignoring the importance of @functools.wraps.
  • Overusing decorators—sometimes simpler solutions are better.

📚 Summary: Key Takeaways

ConceptDescription
DecoratorA function that modifies another function
SyntaxUse @decorator_name above the target function
Use CasesLogging, validation, timing, caching, access control
AdvancedArguments, nesting, functools.wraps, class methods
CautionDon’t lose readability or metadata

🚀 Final Thoughts

Decorators are one of the most elegant and powerful tools in Python. From simple wrappers to advanced meta-programming, they offer immense flexibility. If you’re a beginner, start using decorators in small scripts. If you’re an advanced user, consider how decorators can help enforce consistency, structure, and cleanliness across large projects.

Happy decorating! 🎨🐍

Leave a Comment