In Python, decorators can either be functions or classes. In both cases, decorating adds functionality to existing functions. When we decorate a function with a class, that function becomes an instance of the class. When we define methods in the decorating class, we  can add functionality to the function. This can all be achieved without modifying the original function source code.

This tutorial will demonstrate how classes can be used to decorate the functions we write in our Python code. We’ll go over two use-cases: Decorating a function with a class that accepts no arguments and decorating a function with a class that can accept arguments. If no arguments are passed, our class will fall back to a default value.

What Is a Python Class Decorator?

A Python class decorator adds a class to a function, and it can be achieved without modifying the source code. For example, a function can be decorated with a class that can accept arguments or with a class that can accept no arguments.

I have simplified the examples presented to make it easier to understand. Similar outcomes could be achieved using alternative, simpler approaches, but my goal in this tutorial is to demonstrate how we can use classes to decorate functions and extend their functionality.

This tutorial was also motivated to showcase how to use decorated classes that can accept arguments themselves.

 

How to Create a Python Class Decorator 

The multiply_together function is designed to take two integer values, multiply them together and return their output. Let's consider a scenario in which we would like to add some extra functionality to the function without changing its original source code. Perhaps we would like to square the returned value. We can achieve this using a class decorator.

To decorate a function with a class, we must use the @ syntax followed by our class name above the function definition. Following convention, we will use camel case for our class name. In the class definition, we define two methods — the init constructor and the magic (or dunder) call method.

When we decorate a function with a class, the function is automatically passed as the first argument to the init constructor. We set this function as an attribute in our object. If we print multiply_together now, we can see it is an instance of the power class.

More on PythonAn Introduction to the With Statement in Python

By defining the __call__() method, we can call multiply_together as you could with the original function. Here, we can see that we multiply two-by-two and square the answer.

The call method requires two arguments, which are specified because our original multiply together function required two arguments. We call the multiply together function with these two arguments in this method. This function has been set as self._arg in the object.attribute syntax below. We call this function with two values passed and save the returned value to the variable retval. Finally, we square retval and return the value.

python class decorator power class code
Code for the power class functionality. | Screenshot: Stephen Fordham

The power class extends the functionality of the original multiply_together function. The source code for this example is shown below:

class Power(object):
	def __init__(self, arg):
		self._arg = arg

	def __call__(self, a, b):
		retval = self._arg(a, b)
		return retval ** 2


@Power
def multiply_together(a, b):
	return a * b


print(multiply_together)
print(multiply_together(2, 2))

 

Extending Multiply_Together’s Functionality By Adding Memory

To extend the example presented in the previous section, we can give our power object some memory of the squared values it returned. We can set an empty list to the memory attribute of our object, and append this list every time we call the decorated function. Finally, we can define a method, named “memory” in the code below to return the values stored in the list held by the memory attribute.

This way, we have extended the functionality of our multiply together function further.

python class decorator adding memory to multiply_together
Code showing what happens when youa dd memory to the class. | Screenshot: Stephen Fordham 

The underscore _memory attribute now stores the list of squared values passed to multiply together. We simply call the memory method on the power instance to retrieve the result.

 

Python Class Decorators That Can Accept Arguments

To increase the functionality of the example even further, it would be better to have our class decorator accept arguments. In this way, we could choose which value we would like to use as the exponent with our power class. If no argument is passed to the class decorator, a default exponent value will be set.

Tutorial on Python class decorators. | Video: Kite

 

How to Pass Arguments to the Python Class Decorator

When we pass an argument to the class decorator, that argument and not the function is passed as the argument to the init constructor. In the example presented, we pass the integer value three as an argument to the power class constructor. This value is saved as an attribute, underscore arg (_arg) in the object. The function is then passed as the only argument when we define the call method.

More on PythonA Guide to Managing Datetime in Python

Therefore, if the length of the arguments passed to the call method is equal to one, as would be the case if we pass a decorator argument to the class, this first argument to the call method will be set as the function. We can then define an inner function inside the call method that takes two arguments, “a” and “b.” The call method returns the wrapper function if the length of the arguments passed to call is one. We can call this function with two values passed and finally multiply it by the integer (stored under the attribute _arg) that was originally passed to the class as an argument.

We can then use asterisks followed by the parameter name, here, param_arg, to add flexibility to our call method. This means the parameter can accept a variable number of arguments which are stored in a tuple and allows length checking.

To aid this description, I have included the corresponding example with the accompanying document string below.

python class decorator passing arguments code
Document string for passing arguments with python class decorators. | Screenshot: Stephen Fordham

 

How to Pass No Arguments to the Python Class Decorator

The alternative scenario is when no argument is passed to the class decorator. In this particular case, the function is passed as the first argument to the init constructor.

When we call our decorated function, we pass two integer arguments. Thus, the first conditional in the call method fails and execution proceeds to the else statement. Here, a default value of two is set as the exponent, and the function, stored in the underscore arg attribute of the object, passes the two arguments. The return value is stored in the variable retval. Finally, retval is multiplied by the default exponent and returned.

The source code for both examples shown can be found below.

Now we’ve satisfied both conditions — a class decorator that can either accept arguments or not. If no argument is passed to the class decorator, a default can be set.

python class decorator passing no arguments code
Code showcasing the process of passing no arguments with Python class decorators. | Screenshot: Stephen Fordham
# Full example version with accompanying doc string


class Power(object):
	def __init__(self, arg):
		self._arg = arg

	def __call__(self, *param_arg):
		"""If there are decorator arguments, __call__() is only called once
		as part of the decoration process. You can only give it a single argument,
		which is the function object
		If there are no decorator arguments, the function
		to be decorated is passed to the constructor.
		"""
		if len(param_arg) == 1:
			def wrapper(a, b):
				retval = param_arg[0](a, b)
				return retval ** self._arg
			return wrapper
		else:
			expo = 2
			retval = self._arg(param_arg[0], param_arg[1])
			return retval ** expo


# @Power(3)
# def multiply_together(a, b):
# 	return a * b


@Power
def multiply_together(a, b):
	return a * b


print(multiply_together(2, 2))

 

Python Class Decorator Advantages

The output from the code above could be achieved through simpler means, however, this article focuses on how to use class decorators. Therefore, my goal has been to provide easy-to-follow examples.

Functions can be decorated with classes to extend their functionality. Further, classes that decorate functions can either accept arguments or fall back to a default if no argument is passed. Here, both use-cases are presented to improve the functionality of the original function.

Expert Contributors

Built In’s expert contributor network publishes thoughtful, solutions-oriented stories written by innovative tech professionals. It is the tech industry’s definitive destination for sharing compelling, first-person accounts of problem-solving on the road to innovation.

Learn More

Great Companies Need Great People. That's Where We Come In.

Recruit With Us