Important Python Decorator
In this tutorial, we will learn about some amazing Python decorators that every Python developer should know. These decorators will help to take the code to the next level and do more things in less code.
As we know, Python decorators are a powerful tool that helps to write scalable and clean code. To understand this tutorial, one should be familiar with the decorator. If you are new to decorator then below is a brief introduction to the decorator.
In Python, a decorator is a special type of function that can be used to modify the behavior of another function or class. A decorator takes a function or class as input, performs some modification on it, and returns the modified function or class.
Decorators are denoted by the @symbol followed by the decorator function's name and placed immediately before the function or class to be decorated. When the decorated function or class is called, the decorator function is executed first, and its result is used as the new function or class.
Now, we will see the list of important Python decorator.
The logger is a function that takes a function as an argument and returns a function as output. The output is modified as per the decorator logic. In this section, we will create the logger decorator. Since we don't know the function input, we will pass *arg and **kwarg. The *arg and **kwarg argument allows us to pass an arbitrary number of positional and keyword arguments. Let's see the following implementation of the logger decorator.
We can apply the above decorator on any function and modify its result.
decorator_function = logger(function_argument)
We can also apply the decorator using the @symbol. Let's see the following example.
----- get_text: Function start ----- The Decorator is called ----- get_text: Function end -----
It comes with the Python functools module, we can import it. It returns the values of a function, using a least-recently-used (LRU) algorithm. If the cache is full, it discards the least-used values. This decorator is quite useful in long-running tasks that don't change the output with the same input, such as requesting a static, removing a web page, or querying a database. Following is the example of the @lru_decorator.
To create a cache decorator from scratch, we would start by adding a dictionary as an attribute to the wrapper function. This dictionary would serve as a place to store previously computed values returned by the input function.
When the input function is called, the decorator would first check if the arguments passed to the function have already been computed and stored in the cache; the decorator would return the cached result. Otherwise, the decorator would compute the result and add it to the cache for future use.
The repeat decorator is responsible for the multiple times in a row. It helps debug purposes, stress tests, or automate multiple tasks' repetition. Let's understand the following example -
In the above code, we define a decorator called repeat that takes multiple arguments. We import the wraps decorator which is wrapped around the function being decorated. The wrapper function calls the decorated function a number of times equal to the definite number.
Hi, There Hi, There Hi, There Hi, There Hi, There Hi, There Hi, There Hi, There Hi, There Hi, There
This decorator calculates the execution times of a function and prints the result. It can be used in debugging or monitoring. Let's understand the following example.
process_data took 1.003516 seconds to complete
The @retry decorator in Python is a useful tool for automatically retrying a function when it fails due to certain exceptions. This decorator can be applied to any function and can be customized to retry the function with different parameters based on the type of exception that caused the failure.
Here is an example of how to use the @retry decorator in Python.
In this example, the my_function() function will be retried up to 3 times with a delay of 2 seconds between each retry. The backoff parameter specifies that the delay between retries will double after each retry, and the jitter parameter specifies that the delay between retries will be randomly varied between 1 and 3 seconds to avoid retry storms.
By default, the @retry decorator will retry the function if any exception is raised. However, you can specify a list of exceptions to retry on by passing the retry_on_exception parameter.
In the above code, my_function will only be retried if a ValueError or TypeError is raised.
Note that the @retry decorator is not built-in to Python and needs to be installed separately. There are several libraries that provide the @retry decorator, such as retrying and tenacity.
This decorator is used to preserve the original function's name docstring. Let's understand the following example.
In the above code, we are using the @wraps decorator to preserve the name and docstring of the original add_numbers() function. Without @wraps, the decorated function would have a different name and docstring, which could be confusing and make debugging more difficult.
Here's an example of how to use the add_numbers function.
3 add_number This function adds two numbers
The @rate_limited decorator is used to limit the rate at which a function can be called, by sleeping an amount of time if the function is called too frequently.
In the above code, we are using the rate_limited decorator to limit the rate at which a function can be called. The max_per_second parameter specifies the maximum number of times per second the function can be called.
The decorate function is defined inside the rate_limited decorator and is used to wrap the original function with the rate-limiting logic. We use a list last_time_called to store the last time the function was called, and it allows us to update the previous call time inside the rate_limited_function even though the last_time_called is a mutable object.
The rate_limited_function wraps the original function and calculates the time elapsed since the last call to the function. If the elapsed time is less than the minimum interval, the function sleeps for the difference between the minimum interval and the elapsed time. The last call time is then updated, and the original function is called with the provided arguments.
Hello, world! Hello, world! Hello, world! Hello, world! Hello, world
In the above code, the my_function() function is rate-limited to a maximum of 2 calls per second. When we call the function 5 times in a loop, it only prints "Hello, world!" twice because the rate-limiting decorator sleeps for the appropriate amount of time between calls.
The @dataclass decorator is used to decorate the classes. When we decorate a class with @dataclass decorator, the decorator automatically generates several special methods for the class, such as __init__(), __repr__(), __eq__(), and others, based on the class attributes. This saves the programmer efforts of defining these methods manually, which can be tedious and error-prone, especially for classes with many attributes. It also provides nifty methods off-the-shelf to represent objects nicely, convert them into JSON format, make them immutable, etc.
This decorator was introduced in Python 3.7 and is available in the standard library.
Let's understand the following example.
Car(make='Honda', model='Civic', year=2022, price=25000.0) Car(make='Toyota', model='Corolla', year=2022, price=24000.0) False
In the above code, we define a Car class with four attributes: make, model, year, and price. The @dataclass decorator automatically generates several special methods for the class, including an __init__() method that takes the four attributes as arguments, a __repr__() method that returns a string representation of the object, and an __eq__() method that compares two objects based on their attributes.
We then create two instances of the Car class, car1 and car2, and print them to the console. Finally, we compare car1 and car2 using the == operator, which invokes the __eq__() method generated by the @dataclass decorator.
The @singledispatch decorator is a function decorator in Python's functools module that defines a generic function that behaves differently based on its argument(s) type. It provides a simple way to implement polymorphic behavior in Python.
Below is an example of how to use the @singledispatch decorator.
Example - from functools import singledispatch
This is an integer: 42 This is a string: hello Unknown type argument
In the above code, we define a generic function called myfunc using the @singledispatch decorator. The function is initially defined to print a generic message when called with any argument type.
We then use the register() method to define specific implementations of myfunc for the int and str types. The _ identifier is used to indicate that the function doesn't use the parameter.
The @register decorator can be useful in a situation where your Python script terminates unexpectedly, but we still want to perform some actions before it exits. Registering a function with the @register decorator ensures that the function is called before the script exits, even if an error occurs.
For example, we could use the @register decorator to register a cleanup function that saves our work or prints a message before the script terminates. It can be especially useful in long-running scripts or scripts that perform important tasks.
It @register decorator provides a simple and flexible way to perform actions before a script exits, regardless of the reason for termination.
Decorators are powerful tools for enhancing your code by adding new functionality such as caching, logging, rate limiting, and automatic retry. They can also be used to transform your classes into more versatile data containers.
By leveraging the power of decorators, you can make your code more modular, more efficient, and more flexible. Whether you're working on a large-scale project or a small personal script, decorators can help you take your code to the next level.