[Avg. reading time: 14 minutes]

Decorator

Decorators in Python are a powerful way to modify or extend the behavior of functions or methods without changing their code. Decorators are often used for tasks like logging, authentication, and adding additional functionality to functions. They are denoted by the “@” symbol and are applied above the function they decorate.

def say_hello():
    print("World")

say_hello()

How do we change the output without changing the say hello() function?

wrapper() is not reserved word. It can be anyting.

Use Decorators

# Define a decorator function
def hello_decorator(func):
    def wrapper():
        print("Hello,")
        func()  # Call the original function
    return wrapper

# Use the decorator to modify the behavior of say_hello
@hello_decorator
def say_hello():
    print("World")

# Call the decorated function
say_hello()

When Python sees @decorator_name, it does:

say_hello = hello_decorator(say_hello)

If you want to replace the new line character and the end of the print statement, use end=''

# Define a decorator function
def hello_decorator(func):
    def wrapper():
        print("Hello, ", end='')
        func()  # Call the original function
    return wrapper

# Use the decorator to modify the behavior of say_hello
@hello_decorator
def say_hello():
    print("World")

# Call the decorated function
say_hello()

Multiple functions inside the Decorator

def hello_decorator(func):
    def first_wrapper():
        print("First wrapper, doing something before the second wrapper.")
        #func()
    
    def second_wrapper():
        print("Second wrapper, doing something before the actual function.")
        #func()
    
    def main_wrapper():
        first_wrapper()  # Call the first wrapper
        second_wrapper()  # Then call the second wrapper, which calls the actual function
        func()
    
    return main_wrapper

@hello_decorator
def say_hello():
    print("World")

say_hello()

Multiple Decorators

from functools import wraps
def one(func):
    def one_wrapper():
        print(f"Decorator One: Before function - Called by {func.__name__}")
        func()
        print(f"Decorator One: After function - Called by {func.__name__}")
    return one_wrapper

def two(func):
    def two_wrapper():
        print(f"Decorator Two: Before function - Called by {func.__name__}")
        func()
        print(f"Decorator Two: After function - Called by {func.__name__}")
    return two_wrapper

def three(func):
    def three_wrapper():
        print(f"Decorator Three: Before function - Called by {func.__name__}")
        func()
        print(f"Decorator Three: After function - Called by {func.__name__}")
    return three_wrapper

@one
@two
@three
def say_hello():
    print("Hello, World!")

say_hello()

Decorator Order

one(two(three(say_hello())))

[ONE 
    TWO
        THREE
            SAY_HELLO]

Wraps

@wraps is a decorator from Python’s functools module that preserves the original function’s metadata (like its name, docstring, and annotations) when it’s wrapped by another function.

Without using wraps

def some_decorator(func):
    def wrapper():
        """Wrapper docstring"""
        return func()
    return wrapper

@some_decorator
def hello():
    """Original docstring"""
    print("Hi!")

print(hello.__name__)
print(hello.__doc__)

Using Wraps

from functools import wraps

def some_decorator(func):
    @wraps(func)
    def wrapper():
        """Wrapper docstring"""
        return func()
    return wrapper

@some_decorator
def hello():
    """Original docstring"""
    print("Hi!")

print(hello.__name__)
print(hello.__doc__)

Args & Kwargs

  • *args: This is used to represent positional arguments. It collects all the positional arguments passed to the decorated function as a tuple.
  • **kwargs: This is used to represent keyword arguments. It collects all the keyword arguments (arguments passed with names) as a dictionary.
from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Positional Arguments (*args):", args)
        print("Keyword Arguments (**kwargs):", kwargs)
        result = func(*args, **kwargs)
        return result
    return wrapper

@my_decorator
def example_function(a, b, c=0, d=0):
    print("Function Body:", a, b, c, d)

# Calling the decorated function with different arguments
example_function(1, 2)
example_function(3, 4, c=5)

Popular Example

import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"Execution time of {func.__name__}: {end - start} seconds")
        return result
    return wrapper
    
@timer
def add(x, y):
    """Returns the sum of x and y"""
    return x + y

@timer
def greet(name, message="Hello"):
    """Returns a greeting message with the name"""
    return f"{message}, {name}!"

print(add(2, 3))
print(greet("Rachel"))

The purpose of @wraps is to preserve the metadata of the original function being decorated.

#decorator #wraps #pythonVer 0.3.6

Last change: 2025-12-02