Decorators

In Python, a decorator is a design pattern that allows you to modify the behavior of a function or a class without changing its source code. It helps to enhance or modify the functionality of an existing object dynamically at runtime.

In simple terms, a decorator wraps another function or class and allows you to add extra functionality before or after the wrapped object executes. This allows you to extend the behavior of functions or classes without modifying their original code.

Create a Decorator

To create a decorator, you need to follow these steps:

  1. Define a function that will act as the decorator. This function will take in the function or class to be decorated as an argument.
  2. Inside the decorator function, define a new function or class that will add the additional functionality.
  3. Optionally, you can modify or enhance the original function or class by wrapping it with the new function or class.
  4. Return the new function or class defined in the decorator function.

Decorator Examples

Here's an example of a basic decorator that logs the execution time of a function:

import time

def execution_time_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"Function {func.__name__} took {executio
        n_time} seconds to execute.")
        return result
    return wrapper

@execution_time_decorator
def some_function():
    # Function's code here
    pass

some_function()

In this example, the `executiontimedecorator` function is defined as a decorator. It takes a function as an argument, wraps it with the `wrapper` function that logs the execution time, and returns the wrapped function.

By using the `@executiontimedecorator` syntax above the `somefunction` definition, we apply the decorator to that function. Now, whenever `somefunction` is called, it will automatically log the execution time.

This is just a basic example, and decorators can be used for various purposes, such as logging, authentication, memoization, and more. Decorators enhance code reusability and are an essential part of Python's dynamic programming features.

Basic Decorator

import functools
import time

def how_to_build_a_decorator(func):
    @functools.wraps(func)
    def wrapper_decorator(*args, **kwargs):
        #  Do something before running the function.
        value = func(*args, **kwargs)
        #  Do something after running the function
        return value
    return wrapper_decorator

Debug Decorator

def debug(func):
    """Print the function signature and return value."""
    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        # Do something before running the function.
        args_repr = [repr(a) for a in args]  # 1
        kwargs_repr = [f"{k}= {v!r}" for k, v in kwargs.items()]  # 2
        signature = ", ".join(args_repr + kwargs_repr)  # 3
        print(f"Calling {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} returned {value!r}")  # 4
        # Do something after running the function
        return value
    return wrapper_debug

PLUGINS = dict()

Slow Down Decorator

def slow_down(func):
    """Sleep 1 second before calling the function."""
    @functools.wraps(func)
    def wrapper_slow_down(*args, **kwargs):
        time.sleep(1)
        return func(*args, **kwargs)
    return wrapper_slow_down

Timer Decorator

def timer(func):
    """Print the runtime for decorated function."""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        start_time = time.perf_counter()  # Do something before running the function.
        value = func(*args, **kwargs)
        end_time = time.perf_counter()  # Do something after running the function
        run_time = end_time - start_time
        print(f"Finished {func.__name__!r} in {run_time:.4f} seconds.")
        return value
    return wrapper_timer

Multiple decorators

def make_bold(func):
    def wrapper_make_bold():
        return f"<b>{func()}</b>"
    return wrapper_make_bold


def make_italic(func):
    def wrapper_make_italic():
        return f"<i>{func()}</i>"
    return wrapper_make_italic


if __name__ == "__main__":
    @make_bold
    @make_italic
    def say():
        return "something"

    print(say())