Decorators

Back

Loading concept...

🎁 Python Decorators: The Gift-Wrapping Magic

Imagine this: You have a beautiful gift. But before giving it, you wrap it in shiny paper, add a ribbon, and stick a bow on top. The gift inside stays the same, but now it looks amazing and has extra features!

That’s exactly what decorators do to functions in Python!


🧠 The Big Picture

A decorator is a special wrapper that adds superpowers to your functions—without changing what’s inside them.

graph TD A["Your Function"] --> B["Decorator Wraps It"] B --> C["New Enhanced Function"] C --> D["Same core + Extra powers!"]

1. 🎀 Decorator Basics

What’s Happening?

Think of a decorator like a gift-wrapping station:

  • Your function is the gift
  • The decorator is the wrapper
  • The result? A prettier, more powerful gift!

How It Works

A decorator is just a function that takes another function and returns a new, enhanced version.

def my_decorator(func):
    def wrapper():
        print("Before the gift!")
        func()
        print("After the gift!")
    return wrapper

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

say_hello()

Output:

Before the gift!
Hello!
After the gift!

🎯 Key Insight

The @my_decorator is just a shortcut for:

say_hello = my_decorator(say_hello)

It’s that simple! You’re replacing your function with a wrapped version.


2. 🎁 Decorators with Arguments

The Challenge

What if your function needs to receive information?

Like giving a birthday gift—you need to know whose birthday it is!

The Solution

Make your wrapper accept *args and **kwargs:

def gift_wrapper(func):
    def wrapper(*args, **kwargs):
        print("🎀 Wrapping your gift...")
        result = func(*args, **kwargs)
        print("🎁 Gift delivered!")
        return result
    return wrapper

@gift_wrapper
def greet(name, message="Happy day!"):
    print(f"{name}: {message}")
    return "Done!"

greet("Alice", message="Happy Birthday!")

Output:

🎀 Wrapping your gift...
Alice: Happy Birthday!
🎁 Gift delivered!

🌟 Pro Tip

Always use *args, **kwargs in your wrapper—it makes your decorator work with ANY function!


3. 📚 Stacking Decorators

Multiple Wrappers!

What if you want to add MORE decorations? Like wrapping paper, THEN a ribbon, THEN a bow?

You can stack decorators!

def add_ribbon(func):
    def wrapper(*args, **kwargs):
        print("🎀 Adding ribbon...")
        return func(*args, **kwargs)
    return wrapper

def add_bow(func):
    def wrapper(*args, **kwargs):
        print("🎀 Adding bow...")
        return func(*args, **kwargs)
    return wrapper

@add_bow
@add_ribbon
def give_gift():
    print("🎁 Here's your gift!")

give_gift()

Output:

🎀 Adding bow...
🎀 Adding ribbon...
🎁 Here's your gift!

🔑 Order Matters!

graph TD A["@add_bow #40;outer#41;"] --> B["@add_ribbon #40;inner#41;"] B --> C["give_gift"]

Bottom decorator wraps first, top decorator wraps last!

Think of it like layers:

  • First, wrap the gift in ribbon
  • Then, put a bow on top of the ribbon

4. 🔧 Using functools.wraps

The Hidden Problem

When you wrap a function, you lose its identity!

def simple_decorator(func):
    def wrapper():
        return func()
    return wrapper

@simple_decorator
def my_function():
    """I am a cool function!"""
    pass

print(my_function.__name__)  # 'wrapper' 😢
print(my_function.__doc__)   # None 😢

Your function forgot its own name!

The Fix: functools.wraps

from functools import wraps

def smart_decorator(func):
    @wraps(func)
    def wrapper():
        return func()
    return wrapper

@smart_decorator
def my_function():
    """I am a cool function!"""
    pass

print(my_function.__name__)  # 'my_function' ✅
print(my_function.__doc__)   # 'I am a cool...' ✅

🎯 Always Use @wraps!

It copies:

  • __name__ (function name)
  • __doc__ (docstring)
  • __module__ (where it lives)
  • And more!

Rule of Thumb: Always put @wraps(func) on your wrapper function.


5. 🏛️ Class Decorators

Decorating Entire Classes!

Decorators aren’t just for functions—they can wrap classes too!

Example: Add a Greeting

def add_greeting(cls):
    cls.greet = lambda self: f"Hi, I'm {self.name}!"
    return cls

@add_greeting
class Person:
    def __init__(self, name):
        self.name = name

bob = Person("Bob")
print(bob.greet())  # "Hi, I'm Bob!"

The decorator added a new method to the class!

Example: Track All Instances

def count_instances(cls):
    cls._count = 0
    original_init = cls.__init__

    def new_init(self, *args, **kwargs):
        cls._count += 1
        original_init(self, *args, **kwargs)

    cls.__init__ = new_init
    return cls

@count_instances
class Cat:
    def __init__(self, name):
        self.name = name

c1 = Cat("Whiskers")
c2 = Cat("Fluffy")
print(Cat._count)  # 2

🎮 Putting It All Together

Here’s a real-world example combining everything:

from functools import wraps

def log_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"📞 Calling {func.__name__}")
        result = func(*args, **kwargs)
        print(f"✅ {func.__name__} returned!")
        return result
    return wrapper

def validate_positive(func):
    @wraps(func)
    def wrapper(x):
        if x < 0:
            raise ValueError("Must be positive!")
        return func(x)
    return wrapper

@log_calls
@validate_positive
def square(n):
    """Returns n squared."""
    return n * n

print(square(5))   # 25
print(square.__name__)  # 'square' ✅

✨ Summary

Concept What It Does
Decorator Basics Wraps a function with extra behavior
With Arguments Use *args, **kwargs to handle any inputs
Stacking Apply multiple decorators (bottom-up order)
functools.wraps Preserves the original function’s identity
Class Decorators Enhance entire classes with new features

🚀 You Did It!

Decorators are like magical gift-wrapping paper for your code. They add superpowers without changing what’s inside.

Now you can:

  • ✅ Write basic decorators
  • ✅ Handle functions with arguments
  • ✅ Stack multiple decorators
  • ✅ Preserve function metadata
  • ✅ Decorate entire classes

Go wrap some functions! 🎁

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.