📌 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...") 

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

Output:

Starting... Hello! Finishing... 

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

Output:

slow_function took 1.0023s Done 

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.”