Creating Decorators That Will Always Work

There are several different scenarios in which decorators might apply. It can also be the case that we need to use the same decorator for objects that fall into these different multiple scenarios, for instance, if we want to reuse our decorator and apply it to a function, a class, a method, or a static method.

If we create the decorator, just thinking about supporting only the first type of object we want to decorate, we might notice that the same decorator does not work equally well on a different type of object. A typical example is where we create a decorator to be used on a function, and then we want to apply it to a method of a class, only to realize that it does not work. A similar scenario might occur if we design our decorator for a method, and then we want it to also apply for static methods or class methods.

When designing decorators, we typically think about reusing code, so we will want to use that decorator for functions and methods as well.

Generic decorator function without *args and **kwargs

Defining our decorators with the signature *args and **kwargs will make them work in all cases because it's the most generic kind of signature that we can have. However, sometimes we might want not to use this, and instead define the decorator-wrapping function according to the signature of the original function, mainly because of two reasons:

  • It will be more readable since it resembles the original function.

  • It actually needs to do something with the arguments, so receiving *args and **kwargs wouldn't be convenient.

Consider a case in which we have many functions in our code base that require a particular object to be created from a parameter. For instance, we pass a string, and initialize a driver object with it, repeatedly. Then we think we can remove the duplication by using a decorator that will take care of converting this parameter accordingly.

Example

In this example, we pretend that DBDriver is an object that knows how to connect and run operations on a database, but it needs a connection string. The methods we have in our code are designed to receive a string with the information of the database and require us to create an instance of DBDriver always. The idea of the decorator is that it's going to take the place of this conversion automatically—the function will continue to receive a string, but the decorator will create a DBDriver and pass it to the function, so internally we can assume that we receive the object we need directly.

Applying the decorator to a function

An example of using this in a function is shown below. It's easy to verify that if we pass a string to the function, we get the result done by an instance of DBDriver, so the decorator works as expected:

Get hands-on with 1200+ tech skills courses.