Top 10 Python Built-In Decorators That Optimize Python Code Significantly

Top 10 Python Built-In Decorators That Optimize Python Code Significantly

We all know that Python is a versatile and widely used programming language. It is a high level and mostly used for general-purpose programming. It offers numerous benefits to the developers, and just because of these benefits, the language has become a very popular choice and is used for an extensive range of applications that includes all cutting-edge software technologies like machine learning applications, backend development, web development, and many more. This versatile language is always preferred for a beginner as well as for experienced software developers.

Now as per the topic, here we are going to discuss the decorators in Python. Well, decorators are one of the most useful and powerful tools in Python. It allows programmers to change the behavior of a class or a function. The decorators allow the user to wrap any other function for the purpose of expanding the behavior of the wrapped Function without modifying it on a permanent basis. In this article, we are going to elaborate on the ten best built-in Python decorators that help in optimizing the Python code in a significant manner. Well, these decorators are basically used by software developers to write some clean codes which should be versatile, and we are able to reuse them and even maintain them easily. The decorators are mainly used in multiple Python frameworks and libraries. Flask, Django, and SQLAlchemy are some examples of these frameworks and libraries. Well, there are some astonishing built-in Python decorators who make life easy as a user can able to add complicated functionality to the existing functions or even the class with the help of a single line code. These decorators are mostly used in Python for validation, timing, logging, caching, authentication, and authorization.

Now let's see these amazing decorators one by one and understand them from the base level.

The top 10 built-in decorators in Python help in optimizing the Python codes significantly.

1.@classmethod Decorator.

Here in the class method decorator, there are different methods that are vaulted to a particular class. It is interesting to know that these class methods are never attached to any of the instances of the class. These are the built-in decorators. They are used to create a class method. Now let's see one of the examples and understand this decorator in a better manner.

Example:

class GFGClass:

class_var = 10

@classmethod

def gfg_class_method(cls):

            print("This is a Class method")

            print(f"Value of class variable {cls.class_var}")

GFGClass.gfg_class_method()

Output:

This is a Class method

Value of class variable 10

In the above example, we are able to see a class named GFGClass and also a class method named gfg_class_method which are defined. Here the class method decorator is basically used to symbolize that this is the class method.

In the code, a parameter is in the method definition is actually referring to the class itself, and through it helps, only the class level variable (class_ val )and the methods can be accessed. Here when the class method is called, then it will lock the message that "this is the class method," which is followed by the value of the class_var.

2.@abstractmethod Decorator.

The abstract methods are basically those methods that are proclaimed inside the abstract class without the help of any method definition. These methods are destined to be implemented by the base class, which implements the parent abstract class. It is interesting to know that, actually, the abc module is the provider of this abstract method decorator and which is used to implement the abstract methods.

Now let us see one example to understand this decorator with ease.

Example:

from abc import ABC, abstract method

class Shape(ABC):

            @abstractmethod

            def area(self):

                        pass

class Square(Shape):

            def __init__(self, side):

                        self.side = side

            def area(self):

            return self.side**2

square = Square(10)

print(square.area())

Output

100

In the above example, we can see that there is The Shape class which is defined as the abstract base class when it inherits from the ABC and use the @abstractmethod decorator that basically declares the area() method as an abstract. Here in the code, we are able to see the square class, which is a solid subclass of shape, and it Basically implements the area () method to calculate the area of the square. By using the inheritance ability from the shape, the square must execute all of the abstract methods that are actually declared in shape. For that particular case, the square classical implement area () by just squaring the length of any of the sides of the square.

3.@staticmethod Decorator.

In simple words, if we say, then the static methods are basically the utility methods that are never bound to a class or any instance of the class. It is a built-in decorator which is used for creating static methods.

Now let us see an example to understand this decorator in a better way.

Example:

class GFGClass:

@staticmethod

def gfg_static_method():

            print("This is a static method")

GFGClass.gfg_static_method()

Output

This is a static method.

Well, in the above code, we are able to see the implementation of a static method where a class named GFGClass and a static method named gfg_static_method are particularly defined. Here we can see that the @staticmethod decorator is basically indicating that this is a static method. As compared to the class methods, the static method does not have any special first parameter that will refer to the class itself. These methods are generally used for the utility functions, and it does not depend on the class or any instance of the class.

At the end of the code, when we call gfg_static_method() on the GFGClass, we are able to see the message"This is a static method" when it is logged into the console.

[email protected] Decorators

These decorators are basically used for calling the Function at the time when the Program will be exiting. Here the functionalities are completely provided by the 'atexit'  module. It is interesting to know that this Function is used to perform the cleanup task just before the Program will exits. Well, the cleanups are the task only, and these tasks are done in order to release the resources that are opened. Files are the database connections; both of them are used during the execution of the Program. Now let us see one of the examples of this decorator to understand it in a better way.

Example:

import atexit

@atexit.register

def gfg_exit_handler():

print("Bye, Javatpoint!")

print("Hello, World!")

Output

Hello, Worlds!

Bye, Worlds!

Well, in the above code example, we have seen the implementation of an atexit decorator, and there, we have defined a function named gfg_exit_handler that uses the @atexit.register decorator, which basically registers it to be called when the Program is exiting. When we start running the Program, a message "Hello, Worlds!" will be logged into the console. When the execution is completed or even interrupted, then the gfg_exit_handler() function will be called, and a  message "Bye, Worlds!" will be logged to the console.

Here the gfg_exit_handler() function will be very useful for performing the cleanup task and even closing the resources when the Program is exiting. Here the resources are files or any database connections.

5. @typing.final Decorator.

Here in this decorator, the '@final' decorator, which is taken from the typing module, is used for defining the final class or the method. Well, the final classes will not be inherited, and at the same time, the final methods will not be overridden.

Let's see one of the examples of this final decorator to understand it in a better way.

Example:

import typing

class Base:

            @typing.final

            def done(self):

                        print("Base")

class Child(Base):

            def done(self): # Error: Method "done" cannot override the final method defined in class "Base."

                        print("Child")

@typing.final

class World:

            pass

class Other(World): # Error: Base class "World" is marked final and cannot be subclassed

            pass

In the above example, we are able to see the implementation of the final decorator; here, the @typing.final decorator is basically used to indicate that a method will not be overridden and a class should not be subclassed. Here we have seen a base class it uses the done() method, and it is marked as final by using the @typing.final. Here we are also able to see a Child class which is basically a subclass of the base, and it attempts to override the done () method, which raises the error while executing the code. Likewise, there is a World class that is marked as final by using @typing.final, and it indicates that it should not be a subclass. Well, there are also some other Class attempts to sub-class leaf, and it also raises the error when the code is finally executed. It is interesting to know that by using the @typing.final, we are able to help in preventing unintended changes to some of the critical parts of our code, and then we can make it more vigorous by just enforcing the constraints on how it can be used.

[email protected] Decorator

The enum can also be said to be an enumeration, and basically, it is a set of unique names that exhibit a unique value. They are quite useful when it comes to defining a set of constants that will have a specific meaning. It is interesting to know that the '@unique' decorator, which is provided by the Enum module, is used to just ensure the uniqueness of the enumeration members.

Let us see one of its examples to understand this fine decorator in a better way.

Example:

from enum import Enum, unique

@unique

class Days(Enum):

            MONDAY = 1

            TUESDAY = 2

            WEDNESDAY = 3

            THURSDAY = 2 # ValueError: duplicate values found in <enum 'Days'>: THURSDAY -> TUESDAY

In the above example, we can see the implementation of the Enum decorator. Here firstly, we have an Enum class as 'Days,' which is defined by using the class ' enum.' Well, the class has only four members and which are Monday, Tuesday,  Wednesday, and Thursday, and having values of 1,2,3,2, respectively.

Although Tuesday and Thursday have the same value of 2, so because of that, the unique decorator is raising the value error and presenting the message of "duplicate values found in <enum 'Days'>: THURSDAY-> TUESDAY." This error occurred because of the @unique decorator, which has a task for ensuring that the used values of the enumeration members are unique or not; well, in this case, the values are not unique.

7.@property Decorator

Before elaborating on this decorator, we need to understand the getters and setters. The glitters and setters are basically not the same as compared to the other object-oriented programming languages. One of the main purposes of using the getters and setters in an object-oriented programming language is just to ensure data encapsulation. In Python, they are often used to avoid the Direct access of any class field such that the private variables will not be accessed directly or cannot be modified by the external user.

So if you talk about this decorator, then here, getters and setters are used within the class just to access or do any update of any value of the object variable within the class itself. The property decorator is basically used to define the gators and setters for the class attributes.

Now let us see one of its examples to understand this decorator in a better way.

Example:

class World:

            def __init__(self):

                        self._name = 0

            @property

            def name(self):

            print("Getter Called")

            return self._name

            @name.setter

            def name(self, name):

            print("Setter called")

            self._name = name

p = World()

p.name = 10

print(p.name)

Output:

Setter called

Getter Called

10

Here in the above example, we can see the implementation of a  class that is named the World, and it also has a private attribute as '_name.' Here the Program is basically defining the greater method, which is 'name,' and it allows the name to be retrieved with the help of dot notation. In this example, there is @name.setter decorator, which is used for creating the setter method for just setting the '_name' value. It is interesting to note that @property and the @name.setter decorator are actually defining the getter and setter method for the class attributes, and it is helping in such a way that it makes the work easier with the class data.

[email protected] Decorator

This decorator is one of the widely used, and as per the demand, it was introduced in Python 3.11; it is used to make sure that the placed values of the members of the enumeration follow a certain pattern or constraint. There are some of the preordained constraints that are provided by the enum include constraints named CONTINUE and UNIQUE.

  • Continues: These modules help in ensuring that there should not be any missing value between the highest and the lowest values.
  • Unique: these modules help in checking that each value should have only one name.

Let us take an example to understand the usage of an enum.verify the decorator step by step.

Example:

from enum import Enum, verify, UNIQUE, CONTINUOUS

@verify(UNIQUE)

class Days1(Enum):

            MONDAY = 1

            TUESDAY = 2

            WEDNESDAY = 3

            THURSDAY = 2 #ValueError: aliases found in <enum 'Days'>: THURSDAY -> TUESDAY

@verify(CONTINUOUS)

class Days2(Enum):

            MONDAY = 1

            TUESDAY = 2

            THURSDAY = 4 #ValueError: invalid enum 'Days': missing values 3

In the above example, we are able to see the implementation of two different types of enumeration classes, which are Days1 and Days2 . These classes are defined with the help of the Enum class from the Enam module, and we have applied the verify() decorator to each class.

Well, in the Days1 class, we are able to see the verify() decorator with whom the UNIQUE argument has been used, and it ensures that every member of the Days1 enumeration class exhibits a unique value.

Although, we can see that TUESDAY and THURSDAY exhibit the same value, and there is a ValueError which is raised with the message: “ValueError: aliases found in <enum ‘Days’>: THURSDAY -> TUESDAY."

Secondly, in the day2 class, we are able to see that the verify() decorator with the CONTINUOUS argument has been used, and it is certifying that each member of the Day2 enumeration class creates a continuous sequence starting from the first member. Although, we are able to see that the value of 3 is missing and the value of 4 is directly being used, and also a ValueError is pinged, which shows the message: “ValueError: invalid enum ‘Days’: missing values 3“.

9.@singledispatch Decorator.

Before understanding this decorator, we should understand the generic Function. Well, a function having the same name but having different behavior with respect to the type of argument is called a generic function. Here the @singledispatch decorator in Python is being used to create the generic Function. This Function allows the user to create an overload function that can work with several types of parameters.

Let us take an example to understand the use of this decorator step by step.

Example:

from functools import single dispatch

@singledispatch

def World_func(arg):

            print("Function Call with single argument")

@World_func.register(int)

def _(arg):

            print("Function Called with an integer")

@World_func.register(str)

def _(arg):

            print("Function Called with a string")

@World_func.register(list)

def _(arg):

            print("Function Called with a list")

World_func(1)

World_func([1, 2, 3])

World_func("World")

World_func({1: "World1", 2: "World2"})

Output:

Function Called with an integer

Function Called with a list

Function Called with a string

Function Call with a single argument

In the above example, we are able to see the implementation of a function which is called World_func(). This Function is defined using the @singledispatch decorator, as this decorator commands Python that this Function must be dispatched days on the type of its first argument. There are four additional implementations of the World_func(), which are defined with the help of the @World_func.register decorator. Here every implementation is decorated with the kind of argument that it handles. When we call the World_func() with the help of an argument of a particular type, the implementation that is represented with the corresponding type is called. Make sure that when the call of the Function does not message any of the registered implementations, then by default, it goes to the initial implementation.

10.@lru_cache Decorator

It is interesting to note that the decorator will return the same as lru_cache(maxsize=None) as it creates a thin wrapper around the dictionary lookup for the need of function arguments as it never needs to separate the old values; this decorator is smaller and much faster than lru_cache() but having a size limit.

Now let us take an example to understand the use of this decorator step by step.

Example:

from functools import lru_cache

@lru_cache(maxsize=128)

def fibonacci(n):

            if n < 2:

                        return n

            else:

                        return fibonacci(n-1) + fibonacci(n-2)

# First call to the Function will execute the computation and cache the result

start_time = time.time()

result = fibonacci(10)

print(result)

print("--- %s seconds ---" % (time.time() - start_time))

start_time = time.time()

# Subsequent calls to the Function with the same input will return the cached result

result = Fibonacci (10)

print(result)

print("--- %s seconds ---" % (time.time() - start_time))

Output:

55

--- 0.0009987354278564453 seconds ---

55

--- 0.0 seconds ---

In the above example, we can be able to see the implementation of the Fibonacci sequence. Here we are basically using the memoization with the help of the lru_cache decorator from the functools module. The Fibonacci sequence is basically the series of numbers in which every number is the addition of the two preceding ones, which are starting from 0 and 1.

The Fibonacci function basically takes an integer argument of n and then returns the n-th number in the Fibonacci series, and If the value of n is less than 2, then the Function will return the value n itself. Apart from that, it repeatedly evaluates the addition of the (n-1)-th and (n-2)-th numbers in the series.

Conclusion

As we all know that Python is one of the versatile and largely used programming languages, which makes it a great choice for developers who can implement code that is used for numerous purposes. In the article, we have highlighted the most popular and largely used decorators that are available in Python. The decorators are very handy and quad useful abstractions that are used for extending our code with the help of additional functionalities such as logging, rate limiting, caching, and more. The decorators not only provide the powerful functionalities but it also simplifies our code. With the help of these decorators, we can provide users with some powerful tools for writing concise,  clear, and efficient code in Python. It doesn't matter whether the user is an experienced or beginner developer, as using these operators, we are able to provide a good starting point for improving the code and exploiting the power of Python.