DEV Community

Sameer Qaiser
Sameer Qaiser

Posted on

Python Decorators: A Beginner's Guide

📌 Quick Info

  • Topic: Decorators in Python
  • Target Audience: Beginners who know functions and want to level up
  • Goal: Demystify one of Python's most misunderstood features

1. Introduction

"Decorators looked like magic the first time I saw them. The @ symbol, a function wrapping another function... none of it made sense. Then I learned they're just functions calling functions. Here's how they actually work."


2. The Problem (Repeating Code)

def greet():
    print("Starting...")
    print("Hello!")
    print("Finishing...")

def farewell():
    print("Starting...")
    print("Goodbye!")
    print("Finishing...")
Enter fullscreen mode Exit fullscreen mode

Problem: The “Starting….” and “Finishing….” lines are repeated. If you want to change them, you edit every function.


3. The Solution (Decorators)

def log_before_and_after(func):
    def wrapper():
        print("Starting...")
        func()
        print("Finishing...")
    return wrapper

@log_before_and_after
def greet():
    print("Hello!")

@log_before_and_after
def farewell():
    print("Goodbye!")

greet()
Enter fullscreen mode Exit fullscreen mode

Output:

Starting...
Hello!
Finishing...
Enter fullscreen mode Exit fullscreen mode

The @ symbol applies the decorator to the function- now greet automatically logs before and after.


4. How It Works (Step by Step)

  • Step 1: log_before_and_after takes a function as input.
  • Step 2: Inside, it defines wraper()- a new function that runs extra code before and after calling func().
  • Step 3: It returns wrapper.
  • Step 4: @log_before_and_after above greet is the same as writing greet= log_before_and_after(greet).

That’s the whole trick. Decorators are just functions that take functions and return functions.


5. Real Example (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}s")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "Done"

print(slow_function())
Enter fullscreen mode Exit fullscreen mode

Output:

slow_function took 1.0023s
Done
Enter fullscreen mode Exit fullscreen mode

Now you can time any function by adding @timer above it. No changes to the function itself.


6. Why args* and **kwargs?

Notice wrapper(*args, **kwargs) in the example above. That's what makes the decorator work with any function — no matter how many arguments it takes. Without it, the decorator only works on functions with no arguments.


7. Common Use Cases

Use Case What It Does
Logging Print when a function starts and ends
Timing Measure how long a function takes
Catching Store Results so you don’t recompute
Authentication Check permissions before running a function.
Retry Logic Try again if the function fails

8. What I learned

  • A decorator is a function that wraps another function.
  • @decorator is shorthand for func= decorator (func).
  • **args, *kwargs makes a decorator work with any function.
  • Decorators are everywhere in real code- flask, Django, pytest all use them.
  • Start simple- one decorator that logs is enough to understand the pattern.

9. Conclusion

"Decorators looked like wizardry until I understood they're just functions calling functions. Now I use them for logging and timing everywhere. The @ symbol isn't magic — it's a shortcut.”

Top comments (0)