2023-02-24

Decorators in Python

What are Decorators

Decorators are a powerful feature in Python that allow you to modify or enhance the behavior of functions or classes without changing their source code. A decorator is a function that takes another function or class as input and returns a modified version of it.

In Python, decorators are represented using the @ symbol followed by the name of the decorator function. When a decorated function or class is called, it is actually calling the modified version of the original function or class.

Decorators can be used to perform a wide range of tasks, such as adding logging or timing functionality, enforcing authentication or authorization, caching results, and much more. They are particularly useful when you want to modify the behavior of a function or class in a way that is reusable across multiple functions or classes.

How to Use Decorators

Here is the step to use decorators in Python:

  1. Define the decorator function
    Start by defining a function that will be used as the decorator. This function should take a function or class as an argument and return a modified version of it.

  2. Define the function or class to be decorated
    Next, define the function or class that you want to decorate. This function or class should be defined before the decorator function.

  3. Apply the decorator to the function or class
    To apply the decorator to the function or class, use the '@' symbol followed by the name of the decorator function. Place this syntax on a line immediately before the definition of the function or class to be decorated.

  4. Call the decorated function or class
    Finally, call the decorated function or class as you would normally call a function or class.

Here is an example code of using decorators in Python:

python
def my_decorator(func):
    def wrapper():
        print("Before the function is called.")
        func()
        print("After the function is called.")
    return wrapper

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

say_hello()

In this code, we define a decorator function my_decorator that takes a function as input and returns a modified version of it. The modified version adds some extra behavior before and after the original function is called.

We then use the @ symbol to apply the decorator to the say_hello function. This means that when say_hello is called, it will actually call the modified version of the function created by the my_decorator decorator.

When we run this code, the output will be:

python
Before the function is called.
Hello, world!
After the function is called.

This shows that the decorator successfully added some extra behavior before and after the say_hello function was called.

Decorator Chaining

Decorator chaining is a powerful technique in Python that allows you to apply multiple decorators to a function or class. It enables you to add different layers of functionality to your code without modifying the original function or class.

In Python, decorators are applied in the order that they are listed. When multiple decorators are applied to a function, the result is a chain of decorated functions. Each decorator in the chain modifies the output of the previous decorator.

Decorator chaining can be used to perform a wide range of tasks, such as logging, timing, caching, authentication, and more. It allows you to easily combine different functionalities and create complex behaviors with just a few lines of code.

Here is an example of using decorator chaining in Python:

python
def decorator1(func):
    def wrapper():
        print("Decorator 1")
        func()
    return wrapper

def decorator2(func):
    def wrapper():
        print("Decorator 2")
        func()
    return wrapper

@decorator1
@decorator2
def my_function():
    print("Original function")

my_function()

In this example, we have defined two decorators, decorator1 and decorator2. We then apply both decorators to the my_function function using the @ symbol.

When we run this code, the output will be:

python
Decorator 1
Decorator 2
Original function

This shows that the decorators were applied in the order that they were listed, with decorator1 being applied first and decorator2 being applied second. The output of each decorator was passed to the next decorator in the chain, resulting in the final output of the original function.

Decorator chaining is a powerful technique that can help you write cleaner, more modular code in Python. By combining multiple decorators, you can create complex behaviors that are easy to understand and maintain.

Built-in Decorators in Python

Built-in decorators in Python are pre-defined decorators that can be used to modify the behavior of functions or classes. These decorators are provided by the Python language itself and can be used without requiring any additional code.

Some of the most commonly used built-in decorators in Python include @staticmethod, @classmethod, @property, and @abstractmethod.

The @staticmethod decorator is used to define a static method in a class. Static methods are methods that belong to the class itself rather than to any instance of the class.

The @classmethod decorator is used to define a class method in a class. Class methods are methods that are bound to the class and not the instance of the class.

The @property decorator is used to define a property in a class. Properties allow you to access and modify the attributes of an object in a way that appears to be simple attribute access, but actually executes methods in the background.

The @abstractmethod decorator is used to define an abstract method in a class. Abstract methods are methods that are declared but do not have an implementation. They are meant to be overridden by subclasses of the class that implement the abstract method.

By using built-in decorators in Python, you can easily modify the behavior of your functions or classes without having to write additional code. This can help you write more modular and maintainable code.

Common Uses for Decorators

Decorators are a powerful tool in Python that allow you to modify the behavior of functions or classes. They can be used for a wide range of tasks, such as logging, timing, caching, authentication, and more. In this article, I will discuss some of the most common uses for decorators in Python.

Logging

One common use for decorators is to log the inputs and outputs of a function. This can be useful for debugging and understanding the behavior of a function. Here's an example of a logging decorator:

python
def log(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"Finished {func.__name__} with result={result}")
        return result
    return wrapper

@log
def my_function(x, y):
    return x + y

my_function(1, 2)
python
Calling my_function with args=(1, 2), kwargs={}
Finished my_function with result=3

Timing

Another common use for decorators is to time how long a function takes to execute. This can be useful for identifying slow or inefficient functions. Here's an example of a timing decorator:

python
import time

def timeit(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.2f} seconds")
        return result
    return wrapper

@timeit
def my_function():
    time.sleep(1)

my_function()
python
my_function took 1.00 seconds

Caching

A third common use for decorators is to cache the results of a function to improve performance. This can be useful for functions that are computationally expensive or that require expensive I/O operations. Here's an example of a caching decorator:

python
def cache(func):
    cached_results = {}

    def wrapper(*args):
        if args in cached_results:
            return cached_results[args]
        result = func(*args)
        cached_results[args] = result
        return result

    return wrapper

@cache
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(30))
python
832040

Authentication

A fourth common use for decorators is to enforce authentication or authorization requirements for functions. This can be useful for securing sensitive functions or for controlling access to resources. Here's an example of an authentication decorator:

python
def requires_authentication(func):
    def wrapper(*args, **kwargs):
        if not is_authenticated():
            raise Exception("Not authenticated")
        return func(*args, **kwargs)
    return wrapper

@requires_authentication
def my_function():
    # This function requires authentication
    pass

References

https://www.geeksforgeeks.org/decorators-in-python/
https://peps.python.org/pep-0318/

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!