How to Write Your First Python Decorator

How to Write Your First Python Decorator: A Beginner’s Guide

Introduction: The "Magic" Behind Decorators

Imagine you’re hosting a party. Before guests arrive, you set up the lights, music, and snacks. After they leave, you clean up. What if you could automate this "pre-party" and "post-party" routine every time?

In Python, decorators work similarly—they let you add extra functionality to a function without changing its core code. Whether it’s logging, timing, or access control, decorators help you keep your code clean and reusable.

The best part? Writing your first decorator is easier than you think! Let’s break it down step by step.


What Is a Python Decorator?

A decorator is a function that takes another function and extends its behavior without modifying it directly. Think of it like a wrapper:

def my_decorator(func):
    def wrapper():
        print("Something happens before the function.")
        func()  # Call the original function
        print("Something happens after the function.")
    return wrapper

When you apply @my_decorator to a function, Python automatically "wraps" it:

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

say_hello()

Output:

Something happens before the function.  
Hello!  
Something happens after the function.  

Writing Your First Decorator: A Step-by-Step Example

Let’s build a simple decorator that logs when a function starts and finishes.

Step 1: Define the Decorator

def logger(func):
    def wrapper():
        print(f"Starting {func.__name__}...")
        func()  # Run the original function
        print(f"Finished {func.__name__}!")
    return wrapper

Step 2: Apply It to a Function

@logger
def greet():
    print("Welcome to Python decorators!")

greet()

Output:

Starting greet...  
Welcome to Python decorators!  
Finished greet!  

How It Works:

  1. logger takes greet as input.
  2. wrapper() adds behavior before/after calling greet().
  3. @logger applies the decorator automatically.

Leveling Up: Decorators with Arguments

What if your function has arguments? Use *args and **kwargs:

def logger(func):
    def wrapper(*args, **kwargs):
        print(f"Starting {func.__name__}...")
        result = func(*args, **kwargs)  # Pass arguments to the original function
        print(f"Finished {func.__name__}!")
        return result  # Return the original function's result
    return wrapper

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

print(add(2, 3))

Output:

Starting add...  
Finished add!  
5  

Now your decorator works with any function, regardless of arguments!


Real-World Use Cases for Decorators

Decorators are everywhere in Python. Here are a few practical examples:

  1. Timing Functions

    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:.2f} seconds.")
            return result
        return wrapper
    
    @timer
    def slow_function():
        time.sleep(2)
    
    slow_function()  # Output: "slow_function took 2.00 seconds."
    
  2. Authorization Checks

    def admin_required(func):
        def wrapper(user, *args, **kwargs):
            if user != "admin":
                raise PermissionError("Admins only!")
            return func(user, *args, **kwargs)
        return wrapper
    
    @admin_required
    def delete_database(user):
        print("Database deleted!")
    
    delete_database("admin")  # Works  
    delete_database("guest")  # Raises PermissionError  
    
  3. Caching (Memoization)

    def cache(func):
        stored = {}
        def wrapper(*args):
            if args in stored:
                return stored[args]
            result = func(*args)
            stored[args] = result
            return result
        return wrapper
    
    @cache
    def expensive_calculation(n):
        print(f"Calculating {n}...")
        return n * n
    
    print(expensive_calculation(4))  # Prints "Calculating 4..."  
    print(expensive_calculation(4))  # Returns cached result (no print)  
    

Conclusion: Your Decorator Journey Starts Now!

Decorators are like superpowers for your functions—they let you add reusable features without cluttering your code. You’ve just learned:

✅ How to write a basic decorator
✅ Handling functions with arguments (*args, **kwargs)
✅ Real-world applications (logging, timing, caching)

Now it’s your turn! What’s the first decorator you’ll build?

  • A retry decorator for failed requests?
  • A rate limiter for API calls?
  • A debug decorator to print inputs/outputs?

Drop your ideas below! 👇 #CodingFun

(Hint: Try combining multiple decorators—like @timer @logger—and see what happens!) 🚀

The Magic Behind Python Decorators