Python Decorators

In Python, decorators are a powerful tool that allows you to modify or extend the behavior of functions or classes without changing their actual code. Decorators are widely used in Python frameworks for tasks such as logging, access control, and caching.

1. What is a Python Decorator?

A decorator is a function that takes another function as an argument, adds some functionality to it, and returns the modified function. Decorators are often used to wrap another function in order to modify its behavior.

Basic Syntax of a Decorator:

def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        # Add functionality before the original function
        print(f"Wrapper executed before {original_function.__name__}")
        result = original_function(*args, **kwargs)
        # Add functionality after the original function
        print(f"Wrapper executed after {original_function.__name__}")
        return result
    return wrapper_function

Try It Now

2. Creating and Using a Decorator

Example:

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

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

# Call the decorated function
say_hello()

Try It Now

In this example, my_decorator adds extra functionality before and after the say_hello function.

3. Using Decorators with Arguments

You can also create decorators that accept arguments.

Example:

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def greet():
    print("Hello!")

# This will print "Hello!" three times
greet()

Try It Now

4. Practical Use Cases for Decorators

  • Logging: Automatically log function calls.
  • Authorization: Restrict access to certain parts of the code.
  • Caching: Store function results for faster access.
  • Timing: Measure the execution time of functions.

Example: Logging Decorator

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Function '{func.__name__}' is called with arguments {args} and {kwargs}")
        return func(*args, **kwargs)
    return wrapper

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

# Call the decorated function
result = add(3, 5)
print(f"Result: {result}")

Try It Now

5. Class-Based Decorators

Decorators can also be implemented using classes by defining the __call__ method.

Example:

class MyDecorator:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("Class-based decorator: Before the function call.")
        result = self.func(*args, **kwargs)
        print("Class-based decorator: After the function call.")
        return result

@MyDecorator
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

Try It Now

6. Chaining Multiple Decorators

You can apply multiple decorators to a single function. They are applied from bottom to top.

Example:

def decorator_one(func):
    def wrapper(*args, **kwargs):
        print("Decorator One")
        return func(*args, **kwargs)
    return wrapper

def decorator_two(func):
    def wrapper(*args, **kwargs):
        print("Decorator Two")
        return func(*args, **kwargs)
    return wrapper

@decorator_one
@decorator_two
def say_hello():
    print("Hello!")

say_hello()

Try It Now

In this example, decorator_two is applied first, followed by decorator_one.

Conclusion

Python decorators are a powerful tool for modifying and extending the behavior of functions. They help keep your code clean and reusable. Whether you’re adding logging, handling access control, or measuring performance, decorators provide an elegant solution.